Python AIOHTTP server generator (#1470)

* Astract factory for generators based on connexion

* Add aiohttp server generator

* Fix flask tests

* Normalize python-flask folder names
This commit is contained in:
Jyhess 2019-01-11 16:35:21 +01:00 committed by William Cheng
parent 4652023b7c
commit 80ca67cfda
160 changed files with 5404 additions and 815 deletions

View File

@ -26,9 +26,25 @@ then
fi
# if you've executed sbt assembly previously it will use that instead.
export JAVA_OPTS="${JAVA_OPTS} -XX:MaxPermSize=256M -Xmx1024M -DloggerPath=conf/log4j.properties"
#ags="generate -i modules/openapi-generator/src/test/resources/2_0/petstore.yaml -g python-flask -o samples/server/petstore/flaskConnexion-python2 -DsupportPython2=true $@"
ags="generate -t modules/openapi-generator/src/main/resources/flaskConnexion -i modules/openapi-generator/src/test/resources/3_0/petstore.yaml -g python-flask -o samples/server/petstore/flaskConnexion-python2 -c bin/supportPython2.json -D service $@"
input=modules/openapi-generator/src/test/resources/3_0/petstore.yaml
out_folder=samples/server/openapi3/petstore/python-flask-python2
resources=modules/openapi-generator/src/main/resources/python-flask
# if you've executed sbt assembly previously it will use that instead.
export JAVA_OPTS="${JAVA_OPTS} -Xmx1024M -DloggerPath=conf/log4j.properties"
ags="generate -t $resources -i $input -g python-flask -o $out_folder -c bin/supportPython2.json -D service $@"
rm -rf $out_folder/.openapi*
rm -rf $out_folder/openapi_server
rm $out_folder/.dockerignore
rm $out_folder/.gitignore
rm $out_folder/.travis.yml
rm $out_folder/Dockerfile
rm $out_folder/git_push.sh
rm $out_folder/README.md
rm $out_folder/requirements.txt
rm $out_folder/setup.py
rm $out_folder/test-requirements.txt
rm $out_folder/tox.ini
rm -rf samples/server/petstore/flaskConnexion-python2/*
java $JAVA_OPTS -jar $executable $ags

View File

@ -26,8 +26,25 @@ then
fi
# if you've executed sbt assembly previously it will use that instead.
export JAVA_OPTS="${JAVA_OPTS} -XX:MaxPermSize=256M -Xmx1024M -DloggerPath=conf/log4j.properties"
ags="generate -t modules/openapi-generator/src/main/resources/flaskConnexion -i modules/openapi-generator/src/test/resources/3_0/petstore.yaml -g python-flask -o samples/server/petstore/flaskConnexion -Dservice $@"
input=modules/openapi-generator/src/test/resources/3_0/petstore.yaml
out_folder=samples/server/openapi3/petstore/python-flask
resources=modules/openapi-generator/src/main/resources/python-flask
# if you've executed sbt assembly previously it will use that instead.
export JAVA_OPTS="${JAVA_OPTS} -Xmx1024M -DloggerPath=conf/log4j.properties"
ags="generate -t $resources -i $input -g python-flask -o $out_folder -Dservice $@"
rm -rf $out_folder/.openapi*
rm -rf $out_folder/openapi_server
rm $out_folder/.dockerignore
rm $out_folder/.gitignore
rm $out_folder/.travis.yml
rm $out_folder/Dockerfile
rm $out_folder/git_push.sh
rm $out_folder/README.md
rm $out_folder/requirements.txt
rm $out_folder/setup.py
rm $out_folder/test-requirements.txt
rm $out_folder/tox.ini
rm -rf samples/server/petstore/flaskConnexion/*
java $JAVA_OPTS -jar $executable $ags

View File

@ -1,4 +0,0 @@
#!/bin/sh
./bin/python-flask-petstore.sh
./bin/python-flask-petstore-python2.sh

View File

@ -1,34 +0,0 @@
#!/bin/sh
SCRIPT="$0"
echo "# START SCRIPT: $SCRIPT"
while [ -h "$SCRIPT" ] ; do
ls=`ls -ld "$SCRIPT"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
SCRIPT="$link"
else
SCRIPT=`dirname "$SCRIPT"`/"$link"
fi
done
if [ ! -d "${APP_DIR}" ]; then
APP_DIR=`dirname "$SCRIPT"`/..
APP_DIR=`cd "${APP_DIR}"; pwd`
fi
executable="./modules/openapi-generator-cli/target/openapi-generator-cli.jar"
if [ ! -f "$executable" ]
then
mvn -B clean package
fi
# if you've executed sbt assembly previously it will use that instead.
export JAVA_OPTS="${JAVA_OPTS} -XX:MaxPermSize=256M -Xmx1024M -DloggerPath=conf/log4j.properties"
#ags="generate -i modules/openapi-generator/src/test/resources/2_0/petstore.yaml -g python-flask -o samples/server/petstore/flaskConnexion-python2 -DsupportPython2=true $@"
ags="generate -t modules/openapi-generator/src/main/resources/flaskConnexion -i modules/openapi-generator/src/test/resources/2_0/petstore.yaml -g python-flask -o samples/server/petstore/flaskConnexion-python2 -c bin/supportPython2.json -D service $@"
rm -rf samples/server/petstore/flaskConnexion-python2/*
java $JAVA_OPTS -jar $executable $ags

View File

@ -25,9 +25,20 @@ then
mvn -B clean package
fi
# if you've executed sbt assembly previously it will use that instead.
export JAVA_OPTS="${JAVA_OPTS} -XX:MaxPermSize=256M -Xmx1024M -DloggerPath=conf/log4j.properties"
ags="generate -t modules/openapi-generator/src/main/resources/flaskConnexion -i modules/openapi-generator/src/test/resources/2_0/petstore.yaml -g python-flask -o samples/server/petstore/flaskConnexion -Dservice $@"
generator=python-aiohttp
input=modules/openapi-generator/src/test/resources/2_0/petstore.yaml
out_folder=samples/server/petstore/$generator
resources=modules/openapi-generator/src/main/resources/$generator
# if you've executed sbt assembly previously it will use that instead.
export JAVA_OPTS="${JAVA_OPTS} -Xmx1024M -DloggerPath=conf/log4j.properties"
ags="generate -t $resources -i $input -g $generator -o $out_folder -Dservice $@"
rm -rf $out_folder/.openapi*
rm -rf $out_folder/openapi_server
rm -rf $out_folder/tests*
rm $out_folder/README.md
rm $out_folder/requirements.txt
rm $out_folder/test-requirements.txt
rm -rf samples/server/petstore/flaskConnexion/*
java $JAVA_OPTS -jar $executable $ags

5
bin/python-server-all.sh Executable file
View File

@ -0,0 +1,5 @@
#!/bin/sh
./bin/python-server-aiohttp-petstore.sh
./bin/python-server-flask-petstore.sh
./bin/python-server-flask-petstore-python2.sh

View File

@ -0,0 +1,50 @@
#!/bin/sh
SCRIPT="$0"
echo "# START SCRIPT: $SCRIPT"
while [ -h "$SCRIPT" ] ; do
ls=`ls -ld "$SCRIPT"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
SCRIPT="$link"
else
SCRIPT=`dirname "$SCRIPT"`/"$link"
fi
done
if [ ! -d "${APP_DIR}" ]; then
APP_DIR=`dirname "$SCRIPT"`/..
APP_DIR=`cd "${APP_DIR}"; pwd`
fi
executable="./modules/openapi-generator-cli/target/openapi-generator-cli.jar"
if [ ! -f "$executable" ]
then
mvn -B clean package
fi
generator=python-flask
input=modules/openapi-generator/src/test/resources/2_0/petstore.yaml
out_folder=samples/server/petstore/$generator-python2
resources=modules/openapi-generator/src/main/resources/$generator
# if you've executed sbt assembly previously it will use that instead.
export JAVA_OPTS="${JAVA_OPTS} -Xmx1024M -DloggerPath=conf/log4j.properties"
ags="generate -t $resources -i $input -g $generator -o $out_folder -c bin/supportPython2.json -D service $@"
rm -rf $out_folder/.openapi*
rm -rf $out_folder/openapi_server
rm $out_folder/.dockerignore
rm $out_folder/.gitignore
rm $out_folder/.travis.yml
rm $out_folder/Dockerfile
rm $out_folder/git_push.sh
rm $out_folder/README.md
rm $out_folder/requirements.txt
rm $out_folder/setup.py
rm $out_folder/test-requirements.txt
rm $out_folder/tox.ini
java $JAVA_OPTS -jar $executable $ags

View File

@ -0,0 +1,50 @@
#!/bin/sh
SCRIPT="$0"
echo "# START SCRIPT: $SCRIPT"
while [ -h "$SCRIPT" ] ; do
ls=`ls -ld "$SCRIPT"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
SCRIPT="$link"
else
SCRIPT=`dirname "$SCRIPT"`/"$link"
fi
done
if [ ! -d "${APP_DIR}" ]; then
APP_DIR=`dirname "$SCRIPT"`/..
APP_DIR=`cd "${APP_DIR}"; pwd`
fi
executable="./modules/openapi-generator-cli/target/openapi-generator-cli.jar"
if [ ! -f "$executable" ]
then
mvn -B clean package
fi
generator=python-flask
input=modules/openapi-generator/src/test/resources/2_0/petstore.yaml
out_folder=samples/server/petstore/$generator
resources=modules/openapi-generator/src/main/resources/$generator
# if you've executed sbt assembly previously it will use that instead.
export JAVA_OPTS="${JAVA_OPTS} -Xmx1024M -DloggerPath=conf/log4j.properties"
ags="generate -t $resources -i $input -g $generator -o $out_folder -Dservice $@"
rm -rf $out_folder/.openapi*
rm -rf $out_folder/openapi_server
rm $out_folder/.dockerignore
rm $out_folder/.gitignore
rm $out_folder/.travis.yml
rm $out_folder/Dockerfile
rm $out_folder/git_push.sh
rm $out_folder/README.md
rm $out_folder/requirements.txt
rm $out_folder/setup.py
rm $out_folder/test-requirements.txt
rm $out_folder/tox.ini
java $JAVA_OPTS -jar $executable $ags

View File

@ -5,6 +5,6 @@ If Not Exist %executable% (
)
REM set JAVA_OPTS=%JAVA_OPTS% -Xmx1024M
set ags=generate -i modules\openapi-generator\src\test\resources\2_0\petstore.yaml -g python-flask -o samples\server\petstore\flaskConnexion-python2 -c bin\supportPython2.json -D service
set ags=generate -i modules\openapi-generator\src\test\resources\2_0\petstore.yaml -g python-flask -o samples\server\petstore\python-flask-python2 -c bin\supportPython2.json -D service
java %JAVA_OPTS% -jar %executable% %ags%

View File

@ -5,6 +5,6 @@ If Not Exist %executable% (
)
REM set JAVA_OPTS=%JAVA_OPTS% -Xmx1024M
set ags=generate -i modules\openapi-generator\src\test\resources\2_0\petstore.yaml -g python-flask -o samples\server\petstore\flaskConnexion -D service
set ags=generate -i modules\openapi-generator\src\test\resources\2_0\petstore.yaml -g python-flask -o samples\server\petstore\python-flask -D service
java %JAVA_OPTS% -jar %executable% %ags%

View File

@ -27,7 +27,10 @@ import java.util.Map;
public class CodegenSecurity {
public String name;
public String type;
public String scheme;
public Boolean hasMore, isBasic, isOAuth, isApiKey;
// is Basic is true for all http authentication type. Those are to differentiate basic and bearer authentication
public Boolean isBasicBasic, isBasicBearer;
public Map<String, Object> vendorExtensions = new HashMap<String, Object>();
// ApiKey specific
public String keyParamName;

View File

@ -3230,6 +3230,8 @@ public class DefaultCodegen implements CodegenConfig {
cs.name = key;
cs.type = securityScheme.getType().toString();
cs.isCode = cs.isPassword = cs.isApplication = cs.isImplicit = false;
cs.isBasicBasic = cs.isBasicBearer = false;
cs.scheme = securityScheme.getScheme();
if (SecurityScheme.Type.APIKEY.equals(securityScheme.getType())) {
cs.isBasic = cs.isOAuth = false;
@ -3241,6 +3243,12 @@ public class DefaultCodegen implements CodegenConfig {
} else if (SecurityScheme.Type.HTTP.equals(securityScheme.getType())) {
cs.isKeyInHeader = cs.isKeyInQuery = cs.isKeyInCookie = cs.isApiKey = cs.isOAuth = false;
cs.isBasic = true;
if ("basic".equals(securityScheme.getScheme())) {
cs.isBasicBasic = true;
}
else if ("bearer".equals(securityScheme.getScheme())) {
cs.isBasicBearer = true;
}
} else if (SecurityScheme.Type.OAUTH2.equals(securityScheme.getType())) {
cs.isKeyInHeader = cs.isKeyInQuery = cs.isKeyInCookie = cs.isApiKey = cs.isBasic = false;
cs.isOAuth = true;

View File

@ -0,0 +1,908 @@
/*
* Copyright 2018 OpenAPI-Generator Contributors (https://openapi-generator.tech)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.openapitools.codegen.languages;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Lists;
import com.google.common.collect.Multimap;
import io.swagger.v3.oas.models.Components;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.Operation;
import io.swagger.v3.oas.models.PathItem;
import io.swagger.v3.oas.models.PathItem.HttpMethod;
import io.swagger.v3.oas.models.media.ArraySchema;
import io.swagger.v3.oas.models.media.Schema;
import io.swagger.v3.oas.models.parameters.Parameter;
import io.swagger.v3.oas.models.parameters.RequestBody;
import io.swagger.v3.oas.models.security.SecurityScheme;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.io.FilenameUtils;
import org.openapitools.codegen.*;
import org.openapitools.codegen.utils.ModelUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.util.*;
import static org.openapitools.codegen.utils.StringUtils.camelize;
import static org.openapitools.codegen.utils.StringUtils.underscore;
public class PythonAbstractConnexionServerCodegen extends DefaultCodegen implements CodegenConfig {
private static final Logger LOGGER = LoggerFactory.getLogger(PythonAbstractConnexionServerCodegen.class);
public static final String CONTROLLER_PACKAGE = "controllerPackage";
public static final String DEFAULT_CONTROLLER = "defaultController";
public static final String SUPPORT_PYTHON2 = "supportPython2";
static final String MEDIA_TYPE = "mediaType";
protected int serverPort = 8080;
protected String packageName;
protected String packageVersion;
protected String controllerPackage;
protected String defaultController;
protected Map<Character, String> regexModifiers;
protected boolean fixBodyName;
public PythonAbstractConnexionServerCodegen(String templateDirectory, boolean fixBodyNameValue) {
super();
fixBodyName = fixBodyNameValue;
modelPackage = "models";
testPackage = "test";
languageSpecificPrimitives.clear();
languageSpecificPrimitives.add("int");
languageSpecificPrimitives.add("float");
languageSpecificPrimitives.add("List");
languageSpecificPrimitives.add("Dict");
languageSpecificPrimitives.add("bool");
languageSpecificPrimitives.add("str");
languageSpecificPrimitives.add("datetime");
languageSpecificPrimitives.add("date");
languageSpecificPrimitives.add("file");
languageSpecificPrimitives.add("object");
languageSpecificPrimitives.add("byte");
languageSpecificPrimitives.add("bytearray");
typeMapping.clear();
typeMapping.put("integer", "int");
typeMapping.put("float", "float");
typeMapping.put("number", "float");
typeMapping.put("long", "int");
typeMapping.put("double", "float");
typeMapping.put("array", "List");
typeMapping.put("map", "Dict");
typeMapping.put("boolean", "bool");
typeMapping.put("string", "str");
typeMapping.put("date", "date");
typeMapping.put("DateTime", "datetime");
typeMapping.put("object", "object");
typeMapping.put("file", "file");
typeMapping.put("UUID", "str");
typeMapping.put("byte", "bytearray");
typeMapping.put("ByteArray", "bytearray");
// from https://docs.python.org/3/reference/lexical_analysis.html#keywords
setReservedWordsLowerCase(
Arrays.asList(
// @property
"property",
// python reserved words
"and", "del", "from", "not", "while", "as", "elif", "global", "or", "with",
"assert", "else", "if", "pass", "yield", "break", "except", "import",
"print", "class", "exec", "in", "raise", "continue", "finally", "is",
"return", "def", "for", "lambda", "try", "self", "None", "True", "False", "nonlocal"));
// set the output folder here
outputFolder = "generated-code/connexion";
apiTemplateFiles.put("controller.mustache", ".py");
modelTemplateFiles.put("model.mustache", ".py");
apiTestTemplateFiles().put("controller_test.mustache", ".py");
/*
* Template Location. This is the location which templates will be read from. The generator
* will use the resource stream to attempt to read the templates.
*/
embeddedTemplateDir = templateDir = templateDirectory;
/*
* Additional Properties. These values can be passed to the templates and
* are available in models, apis, and supporting files
*/
additionalProperties.put("serverPort", serverPort);
/*
* Supporting Files. You can write single files for the generator with the
* entire object tree available. If the input file has a suffix of `.mustache
* it will be processed by the template engine. Otherwise, it will be copied
*/
supportingFiles.add(new SupportingFile("README.mustache", "", "README.md"));
supportingFiles.add(new SupportingFile("test-requirements.mustache", "", "test-requirements.txt"));
supportingFiles.add(new SupportingFile("requirements.mustache", "", "requirements.txt"));
regexModifiers = new HashMap<Character, String>();
regexModifiers.put('i', "IGNORECASE");
regexModifiers.put('l', "LOCALE");
regexModifiers.put('m', "MULTILINE");
regexModifiers.put('s', "DOTALL");
regexModifiers.put('u', "UNICODE");
regexModifiers.put('x', "VERBOSE");
cliOptions.add(new CliOption(CodegenConstants.PACKAGE_NAME, "python package name (convention: snake_case).")
.defaultValue("openapi_server"));
cliOptions.add(new CliOption(CodegenConstants.PACKAGE_VERSION, "python package version.")
.defaultValue("1.0.0"));
cliOptions.add(new CliOption(CONTROLLER_PACKAGE, "controller package").
defaultValue("controllers"));
cliOptions.add(new CliOption(DEFAULT_CONTROLLER, "default controller").
defaultValue("default_controller"));
cliOptions.add(new CliOption(SUPPORT_PYTHON2, "support python2").
defaultValue("false"));
cliOptions.add(new CliOption("serverPort", "TCP port to listen to in app.run").
defaultValue("8080"));
}
protected void addSupportingFiles() {
}
@Override
public void processOpts() {
super.processOpts();
if (StringUtils.isEmpty(System.getenv("PYTHON_POST_PROCESS_FILE"))) {
LOGGER.info("Environment variable PYTHON_POST_PROCESS_FILE not defined so the Python code may not be properly formatted. To define it, try 'export PYTHON_POST_PROCESS_FILE=\"/usr/local/bin/yapf -i\"' (Linux/Mac)");
LOGGER.info("NOTE: To enable file post-processing, 'enablePostProcessFile' must be set to `true` (--enable-post-process-file for CLI).");
}
//apiTemplateFiles.clear();
if (additionalProperties.containsKey(CodegenConstants.PACKAGE_NAME)) {
setPackageName((String) additionalProperties.get(CodegenConstants.PACKAGE_NAME));
} else {
setPackageName("openapi_server");
additionalProperties.put(CodegenConstants.PACKAGE_NAME, this.packageName);
}
if (additionalProperties.containsKey(CodegenConstants.PACKAGE_VERSION)) {
setPackageVersion((String) additionalProperties.get(CodegenConstants.PACKAGE_VERSION));
} else {
setPackageVersion("1.0.0");
additionalProperties.put(CodegenConstants.PACKAGE_VERSION, this.packageVersion);
}
if (additionalProperties.containsKey(CONTROLLER_PACKAGE)) {
this.controllerPackage = additionalProperties.get(CONTROLLER_PACKAGE).toString();
} else {
this.controllerPackage = "controllers";
additionalProperties.put(CONTROLLER_PACKAGE, this.controllerPackage);
}
if (additionalProperties.containsKey(DEFAULT_CONTROLLER)) {
this.defaultController = additionalProperties.get(DEFAULT_CONTROLLER).toString();
} else {
this.defaultController = "default_controller";
additionalProperties.put(DEFAULT_CONTROLLER, this.defaultController);
}
if (Boolean.TRUE.equals(additionalProperties.get(SUPPORT_PYTHON2))) {
additionalProperties.put(SUPPORT_PYTHON2, Boolean.TRUE);
typeMapping.put("long", "long");
}
supportingFiles.add(new SupportingFile("__main__.mustache", packageName, "__main__.py"));
supportingFiles.add(new SupportingFile("util.mustache", packageName, "util.py"));
supportingFiles.add(new SupportingFile("__init__.mustache", packageName + File.separatorChar + controllerPackage, "__init__.py"));
supportingFiles.add(new SupportingFile("security_controller_.mustache", packageName + File.separatorChar + controllerPackage, "security_controller_.py"));
supportingFiles.add(new SupportingFile("__init__model.mustache", packageName + File.separatorChar + modelPackage, "__init__.py"));
supportingFiles.add(new SupportingFile("base_model_.mustache", packageName + File.separatorChar + modelPackage, "base_model_.py"));
supportingFiles.add(new SupportingFile("openapi.mustache", packageName + File.separatorChar + "openapi", "openapi.yaml"));
addSupportingFiles();
modelPackage = packageName + "." + modelPackage;
controllerPackage = packageName + "." + controllerPackage;
}
private static String dropDots(String str) {
return str.replaceAll("\\.", "_");
}
@Override
public String apiPackage() {
return controllerPackage;
}
/**
* Configures the type of generator.
*
* @return the CodegenType for this generator
* @see org.openapitools.codegen.CodegenType
*/
@Override
public CodegenType getTag() {
return CodegenType.SERVER;
}
/**
* Returns human-friendly help for the generator. Provide the consumer with help
* tips, parameters here
*
* @return A string value for the help message
*/
@Override
public String getHelp() {
return "Generates a Python server library using the Connexion project. By default, " +
"it will also generate service classes -- which you can disable with the `-Dnoservice` environment variable.";
}
@Override
public String toApiName(String name) {
if (name == null || name.length() == 0) {
return "DefaultController";
}
return camelize(name, false) + "Controller";
}
@Override
public String toApiFilename(String name) {
return underscore(toApiName(name));
}
@Override
public String toApiTestFilename(String name) {
return "test_" + toApiFilename(name);
}
/**
* Escapes a reserved word as defined in the `reservedWords` array. Handle escaping
* those terms here. This logic is only called if a variable matches the reserved words
*
* @return the escaped term
*/
@Override
public String escapeReservedWord(String name) {
if (this.reservedWordsMappings().containsKey(name)) {
return this.reservedWordsMappings().get(name);
}
return "_" + name; // add an underscore to the name
}
/**
* Location to write api files. You can use the apiPackage() as defined when the class is
* instantiated
*/
@Override
public String apiFileFolder() {
return outputFolder + File.separator + apiPackage().replace('.', File.separatorChar);
}
@Override
public String getTypeDeclaration(Schema p) {
if (ModelUtils.isArraySchema(p)) {
ArraySchema ap = (ArraySchema) p;
Schema inner = ap.getItems();
return getSchemaType(p) + "[" + getTypeDeclaration(inner) + "]";
} else if (ModelUtils.isMapSchema(p)) {
Schema inner = ModelUtils.getAdditionalProperties(p);
return getSchemaType(p) + "[str, " + getTypeDeclaration(inner) + "]";
}
return super.getTypeDeclaration(p);
}
@Override
public String getSchemaType(Schema p) {
String schemaType = super.getSchemaType(p);
String type = null;
if (typeMapping.containsKey(schemaType)) {
type = typeMapping.get(schemaType);
if (languageSpecificPrimitives.contains(type)) {
return type;
}
} else {
type = toModelName(schemaType);
}
return type;
}
@Override
public void preprocessOpenAPI(OpenAPI openAPI) {
// need vendor extensions for x-openapi-router-controller
Map<String, PathItem> paths = openAPI.getPaths();
if (paths != null) {
List<String> pathnames = new ArrayList(paths.keySet());
for (String pathname : pathnames) {
PathItem path = paths.get(pathname);
// Fix path parameters to be in snake_case
if (pathname.contains("{")) {
String fixedPath = new String();
for (String token: pathname.substring(1).split("/")) {
if (token.startsWith("{")) {
String snake_case_token = "{" + this.toParamName(token.substring(1, token.length()-1)) + "}";
if(token != snake_case_token) {
token = snake_case_token;
}
}
fixedPath += "/" + token;
}
if (!fixedPath.equals(pathname)) {
LOGGER.warn("Path '" + pathname + "' is not consistant with Python variable names. It will be replaced by '" + fixedPath + "'");
paths.remove(pathname);
paths.put(fixedPath, path);
}
}
Map<HttpMethod, Operation> operationMap = path.readOperationsMap();
if (operationMap != null) {
for (HttpMethod method : operationMap.keySet()) {
Operation operation = operationMap.get(method);
String tag = "default";
if (operation.getTags() != null && operation.getTags().size() > 0) {
tag = operation.getTags().get(0);
}
String operationId = operation.getOperationId();
if (operationId == null) {
operationId = getOrGenerateOperationId(operation, pathname, method.toString());
}
operation.setOperationId(toOperationId(operationId));
if (operation.getExtensions() == null || operation.getExtensions().get("x-openapi-router-controller") == null) {
operation.addExtension(
"x-openapi-router-controller",
controllerPackage + "." + toApiFilename(tag)
);
}
if (operation.getParameters() != null) {
for (Parameter parameter: operation.getParameters()) {
String swaggerParameterName = parameter.getName();
String pythonParameterName = this.toParamName(swaggerParameterName);
if (!swaggerParameterName.equals(pythonParameterName)) {
LOGGER.warn("Parameter name '" + swaggerParameterName + "' is not consistant with Python variable names. It will be replaced by '" + pythonParameterName + "'");
parameter.setName(pythonParameterName);
}
if (swaggerParameterName.isEmpty()) {
LOGGER.error("Missing parameter name in " + pathname + "." + parameter.getIn());
}
}
}
RequestBody body = operation.getRequestBody();
if (fixBodyName && body != null) {
if (body.getExtensions() == null || !body.getExtensions().containsKey("x-body-name")) {
String bodyParameterName = "body";
if (operation.getExtensions() != null && operation.getExtensions().containsKey("x-codegen-request-body-name")) {
bodyParameterName = (String) operation.getExtensions().get("x-codegen-request-body-name");
}
else {
// Used by code generator
operation.addExtension("x-codegen-request-body-name", bodyParameterName);
}
// Used by connexion
body.addExtension("x-body-name", bodyParameterName);
}
}
}
}
}
// Sort path names after variable name fix
List<String> fixedPathnames = new ArrayList(paths.keySet());
Collections.sort(fixedPathnames);
for (String pathname: fixedPathnames) {
PathItem pathItem = paths.remove(pathname);
paths.put(pathname, pathItem);
}
}
addSecurityExtensions(openAPI);
}
private void addSecurityExtension(SecurityScheme securityScheme, String extensionName, String functionName) {
if (securityScheme.getExtensions() == null || ! securityScheme.getExtensions().containsKey(extensionName)) {
securityScheme.addExtension(extensionName, functionName);
}
}
private void addSecurityExtensions(OpenAPI openAPI) {
Components components = openAPI.getComponents();
if (components != null && components.getSecuritySchemes() != null) {
Map<String, SecurityScheme> securitySchemes = components.getSecuritySchemes();
for(String securityName: securitySchemes.keySet()) {
SecurityScheme securityScheme = securitySchemes.get(securityName);
String baseFunctionName = controllerPackage + ".security_controller_.";
switch(securityScheme.getType()) {
case APIKEY:
addSecurityExtension(securityScheme, "x-apikeyInfoFunc", baseFunctionName + "info_from_" + securityName);
break;
case HTTP:
if ("basic".equals(securityScheme.getScheme())) {
addSecurityExtension(securityScheme, "x-basicInfoFunc", baseFunctionName + "info_from_" + securityName);
}
else if ("bearer".equals(securityScheme.getScheme())) {
addSecurityExtension(securityScheme, "x-bearerInfoFunc", baseFunctionName + "info_from_" + securityName);
}
break;
case OPENIDCONNECT:
LOGGER.warn("Security type " + securityScheme.getType().toString() + " is not supported by connextion yet");
case OAUTH2:
addSecurityExtension(securityScheme, "x-tokenInfoFunc", baseFunctionName + "info_from_" + securityName);
addSecurityExtension(securityScheme, "x-scopeValidateFunc", baseFunctionName + "validate_scope_" + securityName);
break;
default:
LOGGER.warn("Unknown security type " + securityScheme.getType().toString());
}
}
}
}
@SuppressWarnings("unchecked")
private static List<Map<String, Object>> getOperations(Map<String, Object> objs) {
List<Map<String, Object>> result = new ArrayList<Map<String, Object>>();
Map<String, Object> apiInfo = (Map<String, Object>) objs.get("apiInfo");
List<Map<String, Object>> apis = (List<Map<String, Object>>) apiInfo.get("apis");
for (Map<String, Object> api : apis) {
result.add((Map<String, Object>) api.get("operations"));
}
return result;
}
private static List<Map<String, Object>> sortOperationsByPath(List<CodegenOperation> ops) {
Multimap<String, CodegenOperation> opsByPath = ArrayListMultimap.create();
for (CodegenOperation op : ops) {
opsByPath.put(op.path, op);
}
List<Map<String, Object>> opsByPathList = new ArrayList<Map<String, Object>>();
for (Map.Entry<String, Collection<CodegenOperation>> entry : opsByPath.asMap().entrySet()) {
Map<String, Object> opsByPathEntry = new HashMap<String, Object>();
opsByPathList.add(opsByPathEntry);
opsByPathEntry.put("path", entry.getKey());
opsByPathEntry.put("operation", entry.getValue());
List<CodegenOperation> operationsForThisPath = Lists.newArrayList(entry.getValue());
operationsForThisPath.get(operationsForThisPath.size() - 1).hasMore = false;
if (opsByPathList.size() < opsByPath.asMap().size()) {
opsByPathEntry.put("hasMore", "true");
}
}
return opsByPathList;
}
@Override
public Map<String, Object> postProcessSupportingFileData(Map<String, Object> objs) {
generateYAMLSpecFile(objs);
for (Map<String, Object> operations : getOperations(objs)) {
@SuppressWarnings("unchecked")
List<CodegenOperation> ops = (List<CodegenOperation>) operations.get("operation");
List<Map<String, Object>> opsByPathList = sortOperationsByPath(ops);
operations.put("operationsByPath", opsByPathList);
}
return super.postProcessSupportingFileData(objs);
}
@Override
public String toVarName(String name) {
// sanitize name
name = sanitizeName(name); // FIXME: a parameter should not be assigned. Also declare the methods parameters as 'final'.
// remove dollar sign
name = name.replaceAll("$", "");
// if it's all uppper case, convert to lower case
if (name.matches("^[A-Z_]*$")) {
name = name.toLowerCase(Locale.ROOT);
}
// underscore the variable name
// petId => pet_id
name = underscore(name);
// remove leading underscore
name = name.replaceAll("^_*", "");
// for reserved word or word starting with number, append _
if (isReservedWord(name) || name.matches("^\\d.*")) {
name = escapeReservedWord(name);
}
return name;
}
@Override
public String toParamName(String name) {
// to avoid conflicts with 'callback' parameter for async call
if ("callback".equals(name)) {
return "param_callback";
}
// should be the same as variable name
return toVarName(name);
}
@Override
public String toModelFilename(String name) {
// underscore the model file name
// PhoneNumber => phone_number
return underscore(dropDots(toModelName(name)));
}
@Override
public String toModelName(String name) {
name = sanitizeName(name); // FIXME: a parameter should not be assigned. Also declare the methods parameters as 'final'.
// remove dollar sign
name = name.replaceAll("$", "");
// model name cannot use reserved keyword, e.g. return
if (isReservedWord(name)) {
LOGGER.warn(name + " (reserved word) cannot be used as model name. Renamed to " + camelize("model_" + name));
name = "model_" + name; // e.g. return => ModelReturn (after camelize)
}
// model name starts with number
if (name.matches("^\\d.*")) {
LOGGER.warn(name + " (model name starts with number) cannot be used as model name. Renamed to " + camelize("model_" + name));
name = "model_" + name; // e.g. 200Response => Model200Response (after camelize)
}
if (!StringUtils.isEmpty(modelNamePrefix)) {
name = modelNamePrefix + "_" + name;
}
if (!StringUtils.isEmpty(modelNameSuffix)) {
name = name + "_" + modelNameSuffix;
}
// camelize the model name
// phone_number => PhoneNumber
return camelize(name);
}
@Override
public String toOperationId(String operationId) {
// throw exception if method name is empty (should not occur as an auto-generated method name will be used)
if (StringUtils.isEmpty(operationId)) {
throw new RuntimeException("Empty method name (operationId) not allowed");
}
// method name cannot use reserved keyword, e.g. return
if (isReservedWord(operationId)) {
LOGGER.warn(operationId + " (reserved word) cannot be used as method name. Renamed to " + underscore(sanitizeName("call_" + operationId)));
operationId = "call_" + operationId;
}
return underscore(sanitizeName(operationId));
}
/**
* Return the default value of the property
*
* @param p OpenAPI property object
* @return string presentation of the default value of the property
*/
@Override
public String toDefaultValue(Schema p) {
if (ModelUtils.isBooleanSchema(p)) {
if (p.getDefault() != null) {
if (p.getDefault().toString().equalsIgnoreCase("false"))
return "False";
else
return "True";
}
} else if (ModelUtils.isDateSchema(p)) {
// TODO
} else if (ModelUtils.isDateTimeSchema(p)) {
// TODO
} else if (ModelUtils.isNumberSchema(p)) {
if (p.getDefault() != null) {
return p.getDefault().toString();
}
} else if (ModelUtils.isIntegerSchema(p)) {
if (p.getDefault() != null) {
return p.getDefault().toString();
}
} else if (ModelUtils.isStringSchema(p)) {
if (p.getDefault() != null) {
return "'" + (String) p.getDefault() + "'";
}
}
return null;
}
@Override
public void setParameterExampleValue(CodegenParameter p) {
String example;
if (p.defaultValue == null) {
example = p.example;
} else {
p.example = p.defaultValue;
return;
}
String type = p.baseType;
if (type == null) {
type = p.dataType;
}
if ("String".equalsIgnoreCase(type) || "str".equalsIgnoreCase(type)) {
if (example == null) {
example = p.paramName + "_example";
}
example = "'" + escapeText(example) + "'";
} else if ("Integer".equals(type) || "int".equals(type)) {
if (p.minimum != null) {
example = "" + (Integer.valueOf(p.minimum) + 1);
}
if (p.maximum != null) {
example = "" + p.maximum;
} else if (example == null) {
example = "56";
}
} else if ("Long".equalsIgnoreCase(type)) {
if (p.minimum != null) {
example = "" + (Long.valueOf(p.minimum) + 1);
}
if (p.maximum != null) {
example = "" + p.maximum;
} else if (example == null) {
example = "789";
}
} else if ("Float".equalsIgnoreCase(type) || "Double".equalsIgnoreCase(type)) {
if (p.minimum != null) {
example = "" + p.minimum;
} else if (p.maximum != null) {
example = "" + p.maximum;
} else if (example == null) {
example = "3.4";
}
} else if ("BOOLEAN".equalsIgnoreCase(type) || "bool".equalsIgnoreCase(type)) {
if (example == null) {
example = "True";
}
} else if ("file".equalsIgnoreCase(type)) {
example = "(BytesIO(b'some file data'), 'file.txt')";
} else if ("Date".equalsIgnoreCase(type)) {
if (example == null) {
example = "2013-10-20";
}
example = "'" + escapeText(example) + "'";
} else if ("DateTime".equalsIgnoreCase(type)) {
if (example == null) {
example = "2013-10-20T19:20:30+01:00";
}
example = "'" + escapeText(example) + "'";
} else if (!languageSpecificPrimitives.contains(type)) {
// type is a model class, e.g. User
example = "{}";
} else {
LOGGER.warn("Type " + type + " not handled properly in setParameterExampleValue");
}
if (p.items != null && p.items.defaultValue != null) {
example = p.items.defaultValue;
}
if (example == null) {
if (Boolean.TRUE.equals(p.isListContainer)) {
example = "[]";
}
else {
example = "None";
}
} else if (Boolean.TRUE.equals(p.isListContainer)) {
if (Boolean.TRUE.equals(p.isBodyParam)) {
example = "[" + example + "]";
}
} else if (Boolean.TRUE.equals(p.isMapContainer)) {
example = "{'key': " + example + "}";
}
p.example = example;
}
public void setPackageName(String packageName) {
this.packageName = packageName;
}
public void setPackageVersion(String packageVersion) {
this.packageVersion = packageVersion;
}
@Override
public String escapeQuotationMark(String input) {
// remove ' to avoid code injection
return input.replace("'", "");
}
@Override
public String escapeUnsafeCharacters(String input) {
// remove multiline comment
return input.replace("'''", "'_'_'");
}
@Override
public String toModelImport(String name) {
String modelImport;
if (StringUtils.startsWithAny(name, "import", "from")) {
modelImport = name;
} else {
modelImport = "from ";
if (!"".equals(modelPackage())) {
modelImport += modelPackage() + ".";
}
modelImport += toModelFilename(name) + " import " + name;
}
return modelImport;
}
@Override
public void postProcessModelProperty(CodegenModel model, CodegenProperty property) {
if (StringUtils.isNotEmpty(property.pattern)) {
addImport(model, "import re");
}
postProcessPattern(property.pattern, property.vendorExtensions);
}
@Override
public Map<String, Object> postProcessModels(Map<String, Object> objs) {
// process enum in models
return postProcessModelsEnum(objs);
}
@Override
public Map<String, Object> postProcessAllModels(Map<String, Object> objs) {
Map<String, Object> result = super.postProcessAllModels(objs);
for (Map.Entry<String, Object> entry : result.entrySet()) {
Map<String, Object> inner = (Map<String, Object>) entry.getValue();
List<Map<String, Object>> models = (List<Map<String, Object>>) inner.get("models");
for (Map<String, Object> mo : models) {
CodegenModel cm = (CodegenModel) mo.get("model");
// Add additional filename information for imports
mo.put("pyImports", toPyImports(cm, cm.imports));
}
}
return result;
}
private List<Map<String, String>> toPyImports(CodegenModel cm, Set<String> imports) {
List<Map<String, String>> pyImports = new ArrayList<>();
for (String im : imports) {
if (!im.equals(cm.classname)) {
HashMap<String, String> pyImport = new HashMap<>();
pyImport.put("import", toModelImport(im));
pyImports.add(pyImport);
}
}
return pyImports;
}
@Override
public void postProcessParameter(CodegenParameter parameter) {
postProcessPattern(parameter.pattern, parameter.vendorExtensions);
}
@Override
public Map<String, Object> postProcessOperationsWithModels(Map<String, Object> objs, List<Object> allModels) {
Map<String, Object> operations = (Map<String, Object>) objs.get("operations");
List<CodegenOperation> operationList = (List<CodegenOperation>) operations.get("operation");
for (CodegenOperation operation : operationList) {
Map<String, String> skipTests = new HashMap<>();
// Set flag to deactivate tests due to connexion issue.
if (operation.consumes != null ) {
if (operation.consumes.size() == 1) {
Map<String, String> consume = operation.consumes.get(0);
if (! "application/json".equals(consume.get(MEDIA_TYPE))) {
skipTests.put("reason", consume.get(MEDIA_TYPE) + " not supported by Connexion");
if ("multipart/form-data".equals(consume.get(MEDIA_TYPE))) {
operation.isMultipart = Boolean.TRUE;
}
}
operation.vendorExtensions.put("x-prefered-consume", consume);
}
else if (operation.consumes.size() > 1) {
Map<String, String> consume = operation.consumes.get(0);
skipTests.put("reason", "Connexion does not support multiple consummes. See https://github.com/zalando/connexion/pull/760");
operation.vendorExtensions.put("x-prefered-consume", consume);
if ("multipart/form-data".equals(consume.get(MEDIA_TYPE))) {
operation.isMultipart = Boolean.TRUE;
}
}
}
else {
// A body without consumes means '*/*' has been used instead of application/json
if (operation.bodyParam != null) {
Map<String, String> consume = new HashMap<>();
consume.put(MEDIA_TYPE, "application/json");
operation.vendorExtensions.put("x-prefered-consume", consume);
skipTests.put("reason", "*/* not supported by Connexion. Use application/json instead. See https://github.com/zalando/connexion/pull/760");
}
}
// Choose to consume 'application/json' if available, else choose the last one.
if (operation.produces != null ) {
for (Map<String, String> produce: operation.produces) {
operation.vendorExtensions.put("x-prefered-produce", produce);
if (produce.get(MEDIA_TYPE).equals("application/json")) {
break;
}
}
}
if (! skipTests.isEmpty()) {
operation.vendorExtensions.put("x-skip-test", skipTests);
}
if (operation.requestBodyExamples != null) {
for (Map<String, String> example: operation.requestBodyExamples) {
if (example.get("contentType") != null && example.get("contentType").equals("application/json")) {
operation.bodyParam.example = example.get("example");
}
}
}
}
return objs;
}
/*
* The openapi pattern spec follows the Perl convention and style of modifiers. Python
* does not support this in as natural a way so it needs to convert it. See
* https://docs.python.org/2/howto/regex.html#compilation-flags for details.
*/
public void postProcessPattern(String pattern, Map<String, Object> vendorExtensions) {
if (pattern != null) {
int i = pattern.lastIndexOf('/');
//Must follow Perl /pattern/modifiers convention
if (pattern.charAt(0) != '/' || i < 2) {
throw new IllegalArgumentException("Pattern must follow the Perl "
+ "/pattern/modifiers convention. " + pattern + " is not valid.");
}
String regex = pattern.substring(1, i).replace("'", "\\'");
List<String> modifiers = new ArrayList<String>();
for (char c : pattern.substring(i).toCharArray()) {
if (regexModifiers.containsKey(c)) {
String modifier = regexModifiers.get(c);
modifiers.add(modifier);
}
}
vendorExtensions.put("x-regex", regex);
vendorExtensions.put("x-modifiers", modifiers);
}
}
@Override
public void postProcessFile(File file, String fileType) {
if (file == null) {
return;
}
String pythonPostProcessFile = System.getenv("PYTHON_POST_PROCESS_FILE");
if (StringUtils.isEmpty(pythonPostProcessFile)) {
return; // skip if PYTHON_POST_PROCESS_FILE env variable is not defined
}
// only process files with py extension
if ("py".equals(FilenameUtils.getExtension(file.toString()))) {
String command = pythonPostProcessFile + " " + file.toString();
try {
Process p = Runtime.getRuntime().exec(command);
int exitValue = p.waitFor();
if (exitValue != 0) {
LOGGER.error("Error running the command ({}). Exit value: {}", command, exitValue);
} else {
LOGGER.info("Successfully executed: " + command);
}
} catch (Exception e) {
LOGGER.error("Error running the command ({}). Exception: {}", command, e.getMessage());
}
}
}
}

View File

@ -0,0 +1,49 @@
/*
* Copyright 2018 OpenAPI-Generator Contributors (https://openapi-generator.tech)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.openapitools.codegen.languages;
import org.openapitools.codegen.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.util.*;
public class PythonAiohttpConnexionServerCodegen extends PythonAbstractConnexionServerCodegen {
private static final Logger LOGGER = LoggerFactory.getLogger(PythonAiohttpConnexionServerCodegen.class);
public PythonAiohttpConnexionServerCodegen() {
super("python-aiohttp", true);
testPackage = "tests";
embeddedTemplateDir = templateDir = "python-aiohttp";
}
/**
* Configures a friendly name for the generator. This will be used by the generator
* to select the library with the -g flag.
*
* @return the friendly name for the generator
*/
@Override
public String getName() {
return "python-aiohttp";
}
@Override
protected void addSupportingFiles() {
supportingFiles.add(new SupportingFile("conftest.mustache", testPackage, "conftest.py"));
supportingFiles.add(new SupportingFile("__init__test.mustache", testPackage, "__init__.py"));
}
}

View File

@ -1,6 +1,5 @@
/*
* Copyright 2018 OpenAPI-Generator Contributors (https://openapi-generator.tech)
* Copyright 2018 SmartBear Software
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -17,19 +16,7 @@
package org.openapitools.codegen.languages;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Lists;
import com.google.common.collect.Multimap;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.Operation;
import io.swagger.v3.oas.models.PathItem;
import io.swagger.v3.oas.models.PathItem.HttpMethod;
import io.swagger.v3.oas.models.media.ArraySchema;
import io.swagger.v3.oas.models.media.Schema;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.io.FilenameUtils;
import org.openapitools.codegen.*;
import org.openapitools.codegen.utils.ModelUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
@ -38,193 +25,11 @@ import java.util.*;
import static org.openapitools.codegen.utils.StringUtils.camelize;
import static org.openapitools.codegen.utils.StringUtils.underscore;
public class PythonFlaskConnexionServerCodegen extends DefaultCodegen implements CodegenConfig {
public class PythonFlaskConnexionServerCodegen extends PythonAbstractConnexionServerCodegen {
private static final Logger LOGGER = LoggerFactory.getLogger(PythonFlaskConnexionServerCodegen.class);
public static final String CONTROLLER_PACKAGE = "controllerPackage";
public static final String DEFAULT_CONTROLLER = "defaultController";
public static final String SUPPORT_PYTHON2 = "supportPython2";
protected int serverPort = 8080;
protected String packageName;
protected String packageVersion;
protected String controllerPackage;
protected String defaultController;
protected Map<Character, String> regexModifiers;
public PythonFlaskConnexionServerCodegen() {
super();
modelPackage = "models";
testPackage = "test";
languageSpecificPrimitives.clear();
languageSpecificPrimitives.add("int");
languageSpecificPrimitives.add("float");
languageSpecificPrimitives.add("List");
languageSpecificPrimitives.add("Dict");
languageSpecificPrimitives.add("bool");
languageSpecificPrimitives.add("str");
languageSpecificPrimitives.add("datetime");
languageSpecificPrimitives.add("date");
languageSpecificPrimitives.add("file");
languageSpecificPrimitives.add("object");
typeMapping.clear();
typeMapping.put("integer", "int");
typeMapping.put("float", "float");
typeMapping.put("number", "float");
typeMapping.put("long", "int");
typeMapping.put("double", "float");
typeMapping.put("array", "List");
typeMapping.put("map", "Dict");
typeMapping.put("boolean", "bool");
typeMapping.put("string", "str");
typeMapping.put("date", "date");
typeMapping.put("DateTime", "datetime");
typeMapping.put("object", "object");
typeMapping.put("file", "file");
typeMapping.put("UUID", "str");
// from https://docs.python.org/3/reference/lexical_analysis.html#keywords
setReservedWordsLowerCase(
Arrays.asList(
// @property
"property",
// python reserved words
"and", "del", "from", "not", "while", "as", "elif", "global", "or", "with",
"assert", "else", "if", "pass", "yield", "break", "except", "import",
"print", "class", "exec", "in", "raise", "continue", "finally", "is",
"return", "def", "for", "lambda", "try", "self", "None", "True", "False", "nonlocal"));
// set the output folder here
outputFolder = "generated-code/connexion";
apiTemplateFiles.put("controller.mustache", ".py");
modelTemplateFiles.put("model.mustache", ".py");
apiTestTemplateFiles().put("controller_test.mustache", ".py");
/*
* Template Location. This is the location which templates will be read from. The generator
* will use the resource stream to attempt to read the templates.
*/
embeddedTemplateDir = templateDir = "flaskConnexion";
/*
* Additional Properties. These values can be passed to the templates and
* are available in models, apis, and supporting files
*/
additionalProperties.put("serverPort", serverPort);
/*
* Supporting Files. You can write single files for the generator with the
* entire object tree available. If the input file has a suffix of `.mustache
* it will be processed by the template engine. Otherwise, it will be copied
*/
supportingFiles.add(new SupportingFile("README.mustache", "", "README.md"));
supportingFiles.add(new SupportingFile("setup.mustache", "", "setup.py"));
supportingFiles.add(new SupportingFile("tox.mustache", "", "tox.ini"));
supportingFiles.add(new SupportingFile("test-requirements.mustache", "", "test-requirements.txt"));
supportingFiles.add(new SupportingFile("requirements.mustache", "", "requirements.txt"));
supportingFiles.add(new SupportingFile("git_push.sh.mustache", "", "git_push.sh"));
supportingFiles.add(new SupportingFile("gitignore.mustache", "", ".gitignore"));
supportingFiles.add(new SupportingFile("travis.mustache", "", ".travis.yml"));
supportingFiles.add(new SupportingFile("Dockerfile.mustache", "", "Dockerfile"));
supportingFiles.add(new SupportingFile("dockerignore.mustache", "", ".dockerignore"));
regexModifiers = new HashMap<Character, String>();
regexModifiers.put('i', "IGNORECASE");
regexModifiers.put('l', "LOCALE");
regexModifiers.put('m', "MULTILINE");
regexModifiers.put('s', "DOTALL");
regexModifiers.put('u', "UNICODE");
regexModifiers.put('x', "VERBOSE");
cliOptions.add(new CliOption(CodegenConstants.PACKAGE_NAME, "python package name (convention: snake_case).")
.defaultValue("openapi_server"));
cliOptions.add(new CliOption(CodegenConstants.PACKAGE_VERSION, "python package version.")
.defaultValue("1.0.0"));
cliOptions.add(new CliOption(CONTROLLER_PACKAGE, "controller package").
defaultValue("controllers"));
cliOptions.add(new CliOption(DEFAULT_CONTROLLER, "default controller").
defaultValue("default_controller"));
cliOptions.add(new CliOption(SUPPORT_PYTHON2, "support python2").
defaultValue("false"));
cliOptions.add(new CliOption("serverPort", "TCP port to listen to in app.run").
defaultValue("8080"));
}
@Override
public void processOpts() {
super.processOpts();
if (StringUtils.isEmpty(System.getenv("PYTHON_POST_PROCESS_FILE"))) {
LOGGER.info("Environment variable PYTHON_POST_PROCESS_FILE not defined so the Python code may not be properly formatted. To define it, try 'export PYTHON_POST_PROCESS_FILE=\"/usr/local/bin/yapf -i\"' (Linux/Mac)");
LOGGER.info("NOTE: To enable file post-processing, 'enablePostProcessFile' must be set to `true` (--enable-post-process-file for CLI).");
}
//apiTemplateFiles.clear();
if (additionalProperties.containsKey(CodegenConstants.PACKAGE_NAME)) {
setPackageName((String) additionalProperties.get(CodegenConstants.PACKAGE_NAME));
} else {
setPackageName("openapi_server");
additionalProperties.put(CodegenConstants.PACKAGE_NAME, this.packageName);
}
if (additionalProperties.containsKey(CodegenConstants.PACKAGE_VERSION)) {
setPackageVersion((String) additionalProperties.get(CodegenConstants.PACKAGE_VERSION));
} else {
setPackageVersion("1.0.0");
additionalProperties.put(CodegenConstants.PACKAGE_VERSION, this.packageVersion);
}
if (additionalProperties.containsKey(CONTROLLER_PACKAGE)) {
this.controllerPackage = additionalProperties.get(CONTROLLER_PACKAGE).toString();
} else {
this.controllerPackage = "controllers";
additionalProperties.put(CONTROLLER_PACKAGE, this.controllerPackage);
}
if (additionalProperties.containsKey(DEFAULT_CONTROLLER)) {
this.defaultController = additionalProperties.get(DEFAULT_CONTROLLER).toString();
} else {
this.defaultController = "default_controller";
additionalProperties.put(DEFAULT_CONTROLLER, this.defaultController);
}
if (Boolean.TRUE.equals(additionalProperties.get(SUPPORT_PYTHON2))) {
additionalProperties.put(SUPPORT_PYTHON2, Boolean.TRUE);
typeMapping.put("long", "long");
}
supportingFiles.add(new SupportingFile("__init__.mustache", packageName, "__init__.py"));
supportingFiles.add(new SupportingFile("__main__.mustache", packageName, "__main__.py"));
supportingFiles.add(new SupportingFile("encoder.mustache", packageName, "encoder.py"));
supportingFiles.add(new SupportingFile("util.mustache", packageName, "util.py"));
supportingFiles.add(new SupportingFile("__init__.mustache", packageName + File.separatorChar + controllerPackage, "__init__.py"));
supportingFiles.add(new SupportingFile("__init__model.mustache", packageName + File.separatorChar + modelPackage, "__init__.py"));
supportingFiles.add(new SupportingFile("base_model_.mustache", packageName + File.separatorChar + modelPackage, "base_model_.py"));
supportingFiles.add(new SupportingFile("__init__test.mustache", packageName + File.separatorChar + testPackage, "__init__.py"));
supportingFiles.add(new SupportingFile("openapi.mustache", packageName + File.separatorChar + "openapi", "openapi.yaml"));
modelPackage = packageName + "." + modelPackage;
controllerPackage = packageName + "." + controllerPackage;
testPackage = packageName + "." + testPackage;
}
private static String dropDots(String str) {
return str.replaceAll("\\.", "_");
}
@Override
public String apiPackage() {
return controllerPackage;
}
/**
* Configures the type of generator.
*
* @return the CodegenType for this generator
* @see org.openapitools.codegen.CodegenType
*/
@Override
public CodegenType getTag() {
return CodegenType.SERVER;
super("python-flask", false);
}
/**
@ -238,493 +43,18 @@ public class PythonFlaskConnexionServerCodegen extends DefaultCodegen implements
return "python-flask";
}
/**
* Returns human-friendly help for the generator. Provide the consumer with help
* tips, parameters here
*
* @return A string value for the help message
*/
@Override
public String getHelp() {
return "Generates a Python server library using the Connexion project. By default, " +
"it will also generate service classes -- which you can disable with the `-Dnoservice` environment variable.";
protected void addSupportingFiles() {
supportingFiles.add(new SupportingFile("gitignore.mustache", "", ".gitignore"));
supportingFiles.add(new SupportingFile("Dockerfile.mustache", "", "Dockerfile"));
supportingFiles.add(new SupportingFile("dockerignore.mustache", "", ".dockerignore"));
supportingFiles.add(new SupportingFile("setup.mustache", "", "setup.py"));
supportingFiles.add(new SupportingFile("tox.mustache", "", "tox.ini"));
supportingFiles.add(new SupportingFile("git_push.sh.mustache", "", "git_push.sh"));
supportingFiles.add(new SupportingFile("travis.mustache", "", ".travis.yml"));
supportingFiles.add(new SupportingFile("encoder.mustache", packageName, "encoder.py"));
supportingFiles.add(new SupportingFile("__init__test.mustache", packageName + File.separatorChar + testPackage, "__init__.py"));
supportingFiles.add(new SupportingFile("__init__.mustache", packageName, "__init__.py"));
testPackage = packageName + "." + testPackage;
}
@Override
public String toApiName(String name) {
if (name == null || name.length() == 0) {
return "DefaultController";
}
return camelize(name, false) + "Controller";
}
@Override
public String toApiFilename(String name) {
return underscore(toApiName(name));
}
@Override
public String toApiTestFilename(String name) {
return "test_" + toApiFilename(name);
}
/**
* Escapes a reserved word as defined in the `reservedWords` array. Handle escaping
* those terms here. This logic is only called if a variable matches the reserved words
*
* @return the escaped term
*/
@Override
public String escapeReservedWord(String name) {
if (this.reservedWordsMappings().containsKey(name)) {
return this.reservedWordsMappings().get(name);
}
return "_" + name; // add an underscore to the name
}
/**
* Location to write api files. You can use the apiPackage() as defined when the class is
* instantiated
*/
@Override
public String apiFileFolder() {
return outputFolder + File.separator + apiPackage().replace('.', File.separatorChar);
}
@Override
public String getTypeDeclaration(Schema p) {
if (ModelUtils.isArraySchema(p)) {
ArraySchema ap = (ArraySchema) p;
Schema inner = ap.getItems();
return getSchemaType(p) + "[" + getTypeDeclaration(inner) + "]";
} else if (ModelUtils.isMapSchema(p)) {
Schema inner = ModelUtils.getAdditionalProperties(p);
return getSchemaType(p) + "[str, " + getTypeDeclaration(inner) + "]";
}
return super.getTypeDeclaration(p);
}
@Override
public String getSchemaType(Schema p) {
String schemaType = super.getSchemaType(p);
String type = null;
if (typeMapping.containsKey(schemaType)) {
type = typeMapping.get(schemaType);
if (languageSpecificPrimitives.contains(type)) {
return type;
}
} else {
type = toModelName(schemaType);
}
return type;
}
@Override
public void preprocessOpenAPI(OpenAPI openAPI) {
// need vendor extensions for x-openapi-router-controller
Map<String, PathItem> paths = openAPI.getPaths();
if (paths != null) {
for (String pathname : paths.keySet()) {
PathItem path = paths.get(pathname);
Map<HttpMethod, Operation> operationMap = path.readOperationsMap();
if (operationMap != null) {
for (HttpMethod method : operationMap.keySet()) {
Operation operation = operationMap.get(method);
String tag = "default";
if (operation.getTags() != null && operation.getTags().size() > 0) {
tag = operation.getTags().get(0);
}
String operationId = operation.getOperationId();
if (operationId == null) {
operationId = getOrGenerateOperationId(operation, pathname, method.toString());
}
operation.setOperationId(toOperationId(operationId));
if (operation.getExtensions() == null || operation.getExtensions().get("x-openapi-router-controller") == null) {
operation.addExtension(
"x-openapi-router-controller",
controllerPackage + "." + toApiFilename(tag)
);
}
}
}
}
}
}
@SuppressWarnings("unchecked")
private static List<Map<String, Object>> getOperations(Map<String, Object> objs) {
List<Map<String, Object>> result = new ArrayList<Map<String, Object>>();
Map<String, Object> apiInfo = (Map<String, Object>) objs.get("apiInfo");
List<Map<String, Object>> apis = (List<Map<String, Object>>) apiInfo.get("apis");
for (Map<String, Object> api : apis) {
result.add((Map<String, Object>) api.get("operations"));
}
return result;
}
private static List<Map<String, Object>> sortOperationsByPath(List<CodegenOperation> ops) {
Multimap<String, CodegenOperation> opsByPath = ArrayListMultimap.create();
for (CodegenOperation op : ops) {
opsByPath.put(op.path, op);
}
List<Map<String, Object>> opsByPathList = new ArrayList<Map<String, Object>>();
for (Map.Entry<String, Collection<CodegenOperation>> entry : opsByPath.asMap().entrySet()) {
Map<String, Object> opsByPathEntry = new HashMap<String, Object>();
opsByPathList.add(opsByPathEntry);
opsByPathEntry.put("path", entry.getKey());
opsByPathEntry.put("operation", entry.getValue());
List<CodegenOperation> operationsForThisPath = Lists.newArrayList(entry.getValue());
operationsForThisPath.get(operationsForThisPath.size() - 1).hasMore = false;
if (opsByPathList.size() < opsByPath.asMap().size()) {
opsByPathEntry.put("hasMore", "true");
}
}
return opsByPathList;
}
@Override
public Map<String, Object> postProcessSupportingFileData(Map<String, Object> objs) {
generateYAMLSpecFile(objs);
for (Map<String, Object> operations : getOperations(objs)) {
@SuppressWarnings("unchecked")
List<CodegenOperation> ops = (List<CodegenOperation>) operations.get("operation");
List<Map<String, Object>> opsByPathList = sortOperationsByPath(ops);
operations.put("operationsByPath", opsByPathList);
}
return super.postProcessSupportingFileData(objs);
}
@Override
public String toVarName(String name) {
// sanitize name
name = sanitizeName(name); // FIXME: a parameter should not be assigned. Also declare the methods parameters as 'final'.
// remove dollar sign
name = name.replaceAll("$", "");
// if it's all uppper case, convert to lower case
if (name.matches("^[A-Z_]*$")) {
name = name.toLowerCase(Locale.ROOT);
}
// underscore the variable name
// petId => pet_id
name = underscore(name);
// remove leading underscore
name = name.replaceAll("^_*", "");
// for reserved word or word starting with number, append _
if (isReservedWord(name) || name.matches("^\\d.*")) {
name = escapeReservedWord(name);
}
return name;
}
@Override
public String toParamName(String name) {
// to avoid conflicts with 'callback' parameter for async call
if ("callback".equals(name)) {
return "param_callback";
}
// should be the same as variable name
return toVarName(name);
}
@Override
public String toModelFilename(String name) {
// underscore the model file name
// PhoneNumber => phone_number
return underscore(dropDots(toModelName(name)));
}
@Override
public String toModelName(String name) {
name = sanitizeName(name); // FIXME: a parameter should not be assigned. Also declare the methods parameters as 'final'.
// remove dollar sign
name = name.replaceAll("$", "");
// model name cannot use reserved keyword, e.g. return
if (isReservedWord(name)) {
LOGGER.warn(name + " (reserved word) cannot be used as model name. Renamed to " + camelize("model_" + name));
name = "model_" + name; // e.g. return => ModelReturn (after camelize)
}
// model name starts with number
if (name.matches("^\\d.*")) {
LOGGER.warn(name + " (model name starts with number) cannot be used as model name. Renamed to " + camelize("model_" + name));
name = "model_" + name; // e.g. 200Response => Model200Response (after camelize)
}
if (!StringUtils.isEmpty(modelNamePrefix)) {
name = modelNamePrefix + "_" + name;
}
if (!StringUtils.isEmpty(modelNameSuffix)) {
name = name + "_" + modelNameSuffix;
}
// camelize the model name
// phone_number => PhoneNumber
return camelize(name);
}
@Override
public String toOperationId(String operationId) {
// throw exception if method name is empty (should not occur as an auto-generated method name will be used)
if (StringUtils.isEmpty(operationId)) {
throw new RuntimeException("Empty method name (operationId) not allowed");
}
// method name cannot use reserved keyword, e.g. return
if (isReservedWord(operationId)) {
LOGGER.warn(operationId + " (reserved word) cannot be used as method name. Renamed to " + underscore(sanitizeName("call_" + operationId)));
operationId = "call_" + operationId;
}
return underscore(sanitizeName(operationId));
}
/**
* Return the default value of the property
*
* @param p OpenAPI property object
* @return string presentation of the default value of the property
*/
@Override
public String toDefaultValue(Schema p) {
if (ModelUtils.isBooleanSchema(p)) {
if (p.getDefault() != null) {
if (p.getDefault().toString().equalsIgnoreCase("false"))
return "False";
else
return "True";
}
} else if (ModelUtils.isDateSchema(p)) {
// TODO
} else if (ModelUtils.isDateTimeSchema(p)) {
// TODO
} else if (ModelUtils.isNumberSchema(p)) {
if (p.getDefault() != null) {
return p.getDefault().toString();
}
} else if (ModelUtils.isIntegerSchema(p)) {
if (p.getDefault() != null) {
return p.getDefault().toString();
}
} else if (ModelUtils.isStringSchema(p)) {
if (p.getDefault() != null) {
return "'" + (String) p.getDefault() + "'";
}
}
return null;
}
@Override
public void setParameterExampleValue(CodegenParameter p) {
String example;
if (p.defaultValue == null) {
example = p.example;
} else {
p.example = p.defaultValue;
return;
}
String type = p.baseType;
if (type == null) {
type = p.dataType;
}
if ("String".equalsIgnoreCase(type) || "str".equalsIgnoreCase(type)) {
if (example == null) {
example = p.paramName + "_example";
}
example = "'" + escapeText(example) + "'";
} else if ("Integer".equals(type) || "int".equals(type)) {
if (p.minimum != null) {
example = "" + (Integer.valueOf(p.minimum) + 1);
}
if (p.maximum != null) {
example = "" + p.maximum;
} else if (example == null) {
example = "56";
}
} else if ("Long".equalsIgnoreCase(type)) {
if (p.minimum != null) {
example = "" + (Long.valueOf(p.minimum) + 1);
}
if (p.maximum != null) {
example = "" + p.maximum;
} else if (example == null) {
example = "789";
}
} else if ("Float".equalsIgnoreCase(type) || "Double".equalsIgnoreCase(type)) {
if (p.minimum != null) {
example = "" + p.minimum;
} else if (p.maximum != null) {
example = "" + p.maximum;
} else if (example == null) {
example = "3.4";
}
} else if ("BOOLEAN".equalsIgnoreCase(type) || "bool".equalsIgnoreCase(type)) {
if (example == null) {
example = "True";
}
} else if ("file".equalsIgnoreCase(type)) {
example = "(BytesIO(b'some file data'), 'file.txt')";
} else if ("Date".equalsIgnoreCase(type)) {
if (example == null) {
example = "2013-10-20";
}
example = "'" + escapeText(example) + "'";
} else if ("DateTime".equalsIgnoreCase(type)) {
if (example == null) {
example = "2013-10-20T19:20:30+01:00";
}
example = "'" + escapeText(example) + "'";
} else if (!languageSpecificPrimitives.contains(type)) {
// type is a model class, e.g. User
example = type + "()";
} else {
LOGGER.warn("Type " + type + " not handled properly in setParameterExampleValue");
}
if (p.items != null && p.items.defaultValue != null) {
example = p.items.defaultValue;
}
if (example == null) {
example = "None";
} else if (Boolean.TRUE.equals(p.isListContainer)) {
if (Boolean.TRUE.equals(p.isBodyParam)) {
example = "[" + example + "]";
}
} else if (Boolean.TRUE.equals(p.isMapContainer)) {
example = "{'key': " + example + "}";
}
p.example = example;
}
public void setPackageName(String packageName) {
this.packageName = packageName;
}
public void setPackageVersion(String packageVersion) {
this.packageVersion = packageVersion;
}
@Override
public String escapeQuotationMark(String input) {
// remove ' to avoid code injection
return input.replace("'", "");
}
@Override
public String escapeUnsafeCharacters(String input) {
// remove multiline comment
return input.replace("'''", "'_'_'");
}
@Override
public String toModelImport(String name) {
String modelImport;
if (StringUtils.startsWithAny(name, "import", "from")) {
modelImport = name;
} else {
modelImport = "from ";
if (!"".equals(modelPackage())) {
modelImport += modelPackage() + ".";
}
modelImport += toModelFilename(name) + " import " + name;
}
return modelImport;
}
@Override
public void postProcessModelProperty(CodegenModel model, CodegenProperty property) {
if (StringUtils.isNotEmpty(property.pattern)) {
addImport(model, "import re");
}
postProcessPattern(property.pattern, property.vendorExtensions);
}
@Override
public Map<String, Object> postProcessModels(Map<String, Object> objs) {
// process enum in models
return postProcessModelsEnum(objs);
}
@Override
public void postProcessParameter(CodegenParameter parameter) {
postProcessPattern(parameter.pattern, parameter.vendorExtensions);
}
/*
* The openapi pattern spec follows the Perl convention and style of modifiers. Python
* does not support this in as natural a way so it needs to convert it. See
* https://docs.python.org/2/howto/regex.html#compilation-flags for details.
*/
public void postProcessPattern(String pattern, Map<String, Object> vendorExtensions) {
if (pattern != null) {
int i = pattern.lastIndexOf('/');
//Must follow Perl /pattern/modifiers convention
if (pattern.charAt(0) != '/' || i < 2) {
throw new IllegalArgumentException("Pattern must follow the Perl "
+ "/pattern/modifiers convention. " + pattern + " is not valid.");
}
String regex = pattern.substring(1, i).replace("'", "\\'");
List<String> modifiers = new ArrayList<String>();
for (char c : pattern.substring(i).toCharArray()) {
if (regexModifiers.containsKey(c)) {
String modifier = regexModifiers.get(c);
modifiers.add(modifier);
}
}
vendorExtensions.put("x-regex", regex);
vendorExtensions.put("x-modifiers", modifiers);
}
}
@Override
public void postProcessFile(File file, String fileType) {
if (file == null) {
return;
}
String pythonPostProcessFile = System.getenv("PYTHON_POST_PROCESS_FILE");
if (StringUtils.isEmpty(pythonPostProcessFile)) {
return; // skip if PYTHON_POST_PROCESS_FILE env variable is not defined
}
// only process files with py extension
if ("py".equals(FilenameUtils.getExtension(file.toString()))) {
String command = pythonPostProcessFile + " " + file.toString();
try {
Process p = Runtime.getRuntime().exec(command);
int exitValue = p.waitFor();
if (exitValue != 0) {
LOGGER.error("Error running the command ({}). Exit value: {}", command, exitValue);
} else {
LOGGER.info("Successfully executed: " + command);
}
} catch (Exception e) {
LOGGER.error("Error running the command ({}). Exception: {}", command, e.getMessage());
}
}
}
}

View File

@ -74,6 +74,7 @@ org.openapitools.codegen.languages.PhpZendExpressivePathHandlerServerCodegen
org.openapitools.codegen.languages.PowerShellClientCodegen
org.openapitools.codegen.languages.PythonClientCodegen
org.openapitools.codegen.languages.PythonFlaskConnexionServerCodegen
org.openapitools.codegen.languages.PythonAiohttpConnexionServerCodegen
org.openapitools.codegen.languages.RClientCodegen
org.openapitools.codegen.languages.RubyClientCodegen
org.openapitools.codegen.languages.RubyOnRailsServerCodegen

View File

@ -0,0 +1,46 @@
# OpenAPI generated server
## Overview
This server was generated by the [OpenAPI Generator](https://openapi-generator.tech) project. By using the
[OpenAPI-Spec](https://openapis.org) from a remote server, you can easily generate a server stub. This
is an example of building a OpenAPI-enabled aiohtpp server.
This example uses the [Connexion](https://github.com/zalando/connexion) library on top of aiohtpp.
## Requirements
Python 3.5.2+
## Usage
To run the server, please execute the following from the root directory:
```
pip3 install -r requirements.txt
python3 -m {{packageName}}
```
and open your browser to here:
```
http://localhost:{{serverPort}}{{contextPath}}/ui/
```
Your OpenAPI definition lives here:
```
http://localhost:{{serverPort}}{{contextPath}}/openapi.json
```
To launch the integration tests, use pytest:
```
sudo pip install -r test-requirements.txt
pytest
```
## Prevent file overriding
After first generation, add edited files to _.openapi-generator-ignore_ to prevent generator to overwrite them. Typically:
```
server/controllers/*
test/*
*.txt
```

View File

@ -0,0 +1,12 @@
import os
import connexion
def main():
options = {
"swagger_ui": True
}
specification_dir = os.path.join(os.path.dirname(__file__), 'openapi')
app = connexion.AioHttpApp(__name__, specification_dir=specification_dir, options=options)
app.add_api('openapi.yaml', arguments={'title': '{{appName}}'}, pass_context_arg_name='request')
app.run(port={{serverPort}})

View File

@ -0,0 +1,5 @@
# coding: utf-8
# import models into model package
{{#models}}{{#model}}from {{modelPackage}}.{{classFilename}} import {{classname}}{{/model}}
{{/models}}

View File

@ -0,0 +1,6 @@
#!/usr/bin/env python3
from . import main
if __name__ == '__main__':
main()

View File

@ -0,0 +1,66 @@
import pprint
import typing
from {{packageName}} import util
T = typing.TypeVar('T')
class Model(object):
# openapiTypes: The key is attribute name and the
# value is attribute type.
openapi_types = {}
# attributeMap: The key is attribute name and the
# value is json key in definition.
attribute_map = {}
@classmethod
def from_dict(cls: T, dikt: dict) -> T:
"""Returns the dict as a model"""
return util.deserialize_model(dikt, cls)
def to_dict(self) -> dict:
"""Returns the model properties as a dict
"""
result = {}
for attr_key, json_key in self.attribute_map.items():
value = getattr(self, attr_key)
if value is None:
continue
if isinstance(value, list):
result[json_key] = list(map(
lambda x: x.to_dict() if hasattr(x, "to_dict") else x,
value
))
elif hasattr(value, "to_dict"):
result[json_key] = value.to_dict()
elif isinstance(value, dict):
result[json_key] = dict(map(
lambda item: (item[0], item[1].to_dict())
if hasattr(item[1], "to_dict") else item,
value.items()
))
else:
result[json_key] = value
return result
def to_str(self) -> str:
"""Returns the string representation of the model
"""
return pprint.pformat(self.to_dict())
def __repr__(self):
"""For `print` and `pprint`"""
return self.to_str()
def __eq__(self, other):
"""Returns true if both objects are equal"""
return self.__dict__ == other.__dict__
def __ne__(self, other):
"""Returns true if both objects are not equal"""
return not self == other

View File

@ -0,0 +1,17 @@
import logging
import pytest
import os
import connexion
@pytest.fixture
def client(loop, aiohttp_client):
logging.getLogger('connexion.operation').setLevel('ERROR')
options = {
"swagger_ui": True
}
specification_dir = os.path.join(os.path.dirname(__file__), '..', '{{packageName}}', 'openapi')
app = connexion.AioHttpApp(__name__, specification_dir=specification_dir, options=options)
app.add_api('openapi.yaml', pass_context_arg_name='request')
return loop.run_until_complete(aiohttp_client(app.app))

View File

@ -0,0 +1,104 @@
from typing import List, Dict
from aiohttp import web
{{#imports}}{{import}}
{{/imports}}
from {{packageName}} import util
{{#operations}}
{{#operation}}
async def {{operationId}}(request: web.Request, {{#allParams}}{{paramName}}{{^required}}=None{{/required}}{{#hasMore}}, {{/hasMore}}{{/allParams}}) -> web.Response:
"""{{#summary}}{{.}}{{/summary}}{{^summary}}{{operationId}}{{/summary}}
{{#notes}}{{.}}{{/notes}}
{{#allParams}}
:param {{paramName}}: {{description}}
{{^isContainer}}
{{#isPrimitiveType}}
:type {{paramName}}: {{>param_type}}
{{/isPrimitiveType}}
{{#isUuid}}
:type {{paramName}}: {{>param_type}}
{{/isUuid}}
{{^isPrimitiveType}}
{{#isFile}}
:type {{paramName}}: werkzeug.datastructures.FileStorage
{{/isFile}}
{{^isFile}}
{{^isUuid}}
:type {{paramName}}: dict | bytes
{{/isUuid}}
{{/isFile}}
{{/isPrimitiveType}}
{{/isContainer}}
{{#isListContainer}}
{{#items}}
{{#isPrimitiveType}}
:type {{paramName}}: List[{{>param_type}}]
{{/isPrimitiveType}}
{{^isPrimitiveType}}
:type {{paramName}}: list | bytes
{{/isPrimitiveType}}
{{/items}}
{{/isListContainer}}
{{#isMapContainer}}
{{#items}}
{{#isPrimitiveType}}
:type {{paramName}}: Dict[str, {{>param_type}}]
{{/isPrimitiveType}}
{{^isPrimitiveType}}
:type {{paramName}}: dict | bytes
{{/isPrimitiveType}}
{{/items}}
{{/isMapContainer}}
{{/allParams}}
"""
{{#allParams}}
{{^isContainer}}
{{#isDate}}
{{paramName}} = util.deserialize_date({{paramName}})
{{/isDate}}
{{#isDateTime}}
{{paramName}} = util.deserialize_datetime({{paramName}})
{{/isDateTime}}
{{^isPrimitiveType}}
{{^isFile}}
{{^isUuid}}
{{paramName}} = {{baseType}}.from_dict({{paramName}})
{{/isUuid}}
{{/isFile}}
{{/isPrimitiveType}}
{{/isContainer}}
{{#isListContainer}}
{{#items}}
{{#isDate}}
{{paramName}} = [util.deserialize_date(s) for s in {{paramName}}]
{{/isDate}}
{{#isDateTime}}
{{paramName}} = [util.deserialize_datetime(s) for s in {{paramName}}]
{{/isDateTime}}
{{#complexType}}
{{paramName}} = [{{complexType}}.from_dict(d) for d in {{paramName}}]
{{/complexType}}
{{/items}}
{{/isListContainer}}
{{#isMapContainer}}
{{#items}}
{{#isDate}}
{{paramName}} = {k: util.deserialize_date(v) for k, v in {{paramName}}}
{{/isDate}}
{{#isDateTime}}
{{paramName}} = {k: util.deserialize_datetime(v) for k, v in {{paramName}}}
{{/isDateTime}}
{{#complexType}}
{{paramName}} = {k: {{baseType}}.from_dict(v) for k, v in {{paramName}}}
{{/complexType}}
{{/items}}
{{/isMapContainer}}
{{/allParams}}
return web.Response(status=200)
{{/operation}}
{{/operations}}

View File

@ -0,0 +1,68 @@
# coding: utf-8
import pytest
import json
from aiohttp import web
{{#operations}}
{{#operation}}
{{#isMultipart}}
from aiohttp import FormData
{{/isMultipart}}
{{/operation}}
{{/operations}}
{{#imports}}{{import}}
{{/imports}}
{{#operations}}
{{#operation}}
{{#vendorExtensions.x-skip-test}}
@pytest.mark.skip("{{reason}}")
{{/vendorExtensions.x-skip-test}}
async def test_{{operationId}}(client):
"""Test case for {{{operationId}}}
{{{summary}}}
"""
{{#bodyParam}}
{{paramName}} = {{{example}}}
{{/bodyParam}}
{{#queryParams}}
{{#-first}}params = [{{/-first}}{{^-first}} {{/-first}}('{{paramName}}', {{{example}}}){{#hasMore}},{{/hasMore}}{{#-last}}]{{/-last}}
{{/queryParams}}
headers = { {{#vendorExtensions.x-prefered-produce}}
'Accept': '{{mediaType}}',{{/vendorExtensions.x-prefered-produce}}{{#vendorExtensions.x-prefered-consume}}
'Content-Type': '{{mediaType}}',{{/vendorExtensions.x-prefered-consume}}{{#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}}
{{#isMultipart}}
{{#-first}}
data = FormData()
{{/-first}}
data.add_field('{{paramName}}', {{{example}}})
{{/isMultipart}}
{{^isMultipart}}
{{#-first}}
data = {
{{/-first}}
'{{paramName}}': {{{example}}}{{#hasMore}},{{/hasMore}}
{{^hasMore}}
}
{{/hasMore}}
{{/isMultipart}}
{{/formParams}}
response = await client.request(
method='{{httpMethod}}',
path='{{#contextPath}}{{{.}}}{{/contextPath}}{{{path}}}'{{#pathParams}}{{#-first}}.format({{/-first}}{{paramName}}={{{example}}}{{#hasMore}}, {{/hasMore}}{{^hasMore}}){{/hasMore}}{{/pathParams}},
headers=headers,{{#bodyParam}}
json={{paramName}},{{/bodyParam}}{{#formParams}}{{#-first}}
data=data,{{/-first}}{{/formParams}}{{#queryParams}}{{#-first}}
params=params,{{/-first}}{{/queryParams}}
)
assert response.status == 200, 'Response body is : ' + (await response.read()).decode('utf-8')
{{/operation}}
{{/operations}}

View File

@ -0,0 +1,162 @@
# coding: utf-8
from datetime import date, datetime
from typing import List, Dict, Type
from {{modelPackage}}.base_model_ import Model
{{#models}}
{{#model}}
{{#pyImports}}
{{import}}
{{/pyImports}}
{{/model}}
{{/models}}
from {{packageName}} import util
{{#models}}
{{#model}}
class {{classname}}(Model):
"""NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
Do not edit the class manually.
"""{{#allowableValues}}
"""
allowed enum values
"""
{{#enumVars}}
{{name}} = {{{value}}}{{^-last}}
{{/-last}}
{{/enumVars}}{{/allowableValues}}
def __init__(self{{#vars}}, {{name}}: {{dataType}}={{#defaultValue}}{{{defaultValue}}}{{/defaultValue}}{{^defaultValue}}None{{/defaultValue}}{{/vars}}):
"""{{classname}} - a model defined in OpenAPI
{{#vars}}
:param {{name}}: The {{name}} of this {{classname}}.
{{/vars}}
"""
self.openapi_types = {
{{#vars}}
'{{name}}': {{dataType}}{{#hasMore}},{{/hasMore}}
{{/vars}}
}
self.attribute_map = {
{{#vars}}
'{{name}}': '{{baseName}}'{{#hasMore}},{{/hasMore}}
{{/vars}}
}
{{#vars}}{{#-first}}
{{/-first}}
self._{{name}} = {{name}}
{{/vars}}
@classmethod
def from_dict(cls, dikt: dict) -> '{{classname}}':
"""Returns the dict as a model
:param dikt: A dict.
:return: The {{name}} of this {{classname}}.
"""
return util.deserialize_model(dikt, cls){{#vars}}{{#-first}}
{{/-first}}
@property
def {{name}}(self):
"""Gets the {{name}} of this {{classname}}.
{{#description}}
{{{description}}}
{{/description}}
:return: The {{name}} of this {{classname}}.
:rtype: {{dataType}}
"""
return self._{{name}}
@{{name}}.setter
def {{name}}(self, {{name}}):
"""Sets the {{name}} of this {{classname}}.
{{#description}}
{{{description}}}
{{/description}}
:param {{name}}: The {{name}} of this {{classname}}.
:type {{name}}: {{dataType}}
"""
{{#isEnum}}
allowed_values = [{{#allowableValues}}{{#values}}"{{{this}}}"{{^-last}}, {{/-last}}{{/values}}{{/allowableValues}}]
{{#isContainer}}
{{#isListContainer}}
if not set({{{name}}}).issubset(set(allowed_values)):
raise ValueError(
"Invalid values for `{{{name}}}` [{0}], must be a subset of [{1}]"
.format(", ".join(map(str, set({{{name}}}) - set(allowed_values))),
", ".join(map(str, allowed_values)))
)
{{/isListContainer}}
{{#isMapContainer}}
if not set({{{name}}}.keys()).issubset(set(allowed_values)):
raise ValueError(
"Invalid keys in `{{{name}}}` [{0}], must be a subset of [{1}]"
.format(", ".join(map(str, set({{{name}}}.keys()) - set(allowed_values))),
", ".join(map(str, allowed_values)))
)
{{/isMapContainer}}
{{/isContainer}}
{{^isContainer}}
if {{{name}}} not in allowed_values:
raise ValueError(
"Invalid value for `{{{name}}}` ({0}), must be one of {1}"
.format({{{name}}}, allowed_values)
)
{{/isContainer}}
{{/isEnum}}
{{^isEnum}}
{{#required}}
if {{name}} is None:
raise ValueError("Invalid value for `{{name}}`, must not be `None`")
{{/required}}
{{#hasValidation}}
{{#maxLength}}
if {{name}} is not None and len({{name}}) > {{maxLength}}:
raise ValueError("Invalid value for `{{name}}`, length must be less than or equal to `{{maxLength}}`")
{{/maxLength}}
{{#minLength}}
if {{name}} is not None and len({{name}}) < {{minLength}}:
raise ValueError("Invalid value for `{{name}}`, length must be greater than or equal to `{{minLength}}`")
{{/minLength}}
{{#maximum}}
if {{name}} is not None and {{name}} >{{#exclusiveMaximum}}={{/exclusiveMaximum}} {{maximum}}:
raise ValueError("Invalid value for `{{name}}`, must be a value less than {{^exclusiveMaximum}}or equal to {{/exclusiveMaximum}}`{{maximum}}`")
{{/maximum}}
{{#minimum}}
if {{name}} is not None and {{name}} <{{#exclusiveMinimum}}={{/exclusiveMinimum}} {{minimum}}:
raise ValueError("Invalid value for `{{name}}`, must be a value greater than {{^exclusiveMinimum}}or equal to {{/exclusiveMinimum}}`{{minimum}}`")
{{/minimum}}
{{#pattern}}
if {{name}} is not None and not re.search(r'{{{vendorExtensions.x-regex}}}', {{name}}{{#vendorExtensions.x-modifiers}}{{#-first}}, flags={{/-first}}re.{{.}}{{^-last}} | {{/-last}}{{/vendorExtensions.x-modifiers}}):
raise ValueError("Invalid value for `{{name}}`, must be a follow pattern or equal to `{{{pattern}}}`")
{{/pattern}}
{{#maxItems}}
if {{name}} is not None and len({{name}}) > {{maxItems}}:
raise ValueError("Invalid value for `{{name}}`, number of items must be less than or equal to `{{maxItems}}`")
{{/maxItems}}
{{#minItems}}
if {{name}} is not None and len({{name}}) < {{minItems}}:
raise ValueError("Invalid value for `{{name}}`, number of items must be greater than or equal to `{{minItems}}`")
{{/minItems}}
{{/hasValidation}}
{{/isEnum}}
self._{{name}} = {{name}}{{^-last}}
{{/-last}}
{{/vars}}
{{/model}}
{{/models}}

View File

@ -0,0 +1,3 @@
connexion[aiohttp,swagger-ui] == 2.0.2
swagger-ui-bundle == 0.0.2
aiohttp_jinja2 == 1.1.0

View File

@ -0,0 +1,59 @@
from typing import List
{{#authMethods}}
{{#isOAuth}}
def info_from_{{name}}(token: str) -> dict:
"""
Validate and decode token.
Returned value will be passed in 'token_info' parameter of your operation function, if there is one.
'sub' or 'uid' will be set in 'user' parameter of your operation function, if there is one.
'scope' or 'scopes' will be passed to scope validation function.
Should return None if token is invalid or does not allow access to called API.
"""
return {'scopes': ['read:pets', 'write:pets'], 'uid': 'user_id'}
def validate_scope_{{name}}(required_scopes: List[str], token_scopes: List[str]) -> bool:
""" Validate required scopes are included in token scope """
return set(required_scopes).issubset(set(token_scopes))
{{/isOAuth}}
{{#isApiKey}}
def info_from_{{name}}(api_key: str, required_scopes: None) -> dict:
"""
Check and retrieve authentication information from api_key.
Returned value will be passed in 'token_info' parameter of your operation function, if there is one.
'sub' or 'uid' will be set in 'user' parameter of your operation function, if there is one.
Should return None if api_key is invalid or does not allow access to called API.
"""
return {'uid': 'user_id'}
{{/isApiKey}}
{{#isBasicBasic}}
def info_from_{{name}}(username: str, password: str, required_scopes: None) -> dict:
"""
Check and retrieve authentication information from basic auth.
Returned value will be passed in 'token_info' parameter of your operation function, if there is one.
'sub' or 'uid' will be set in 'user' parameter of your operation function, if there is one.
Should return None if auth is invalid or does not allow access to called API.
"""
return {'uid': 'user_id'}
{{/isBasicBasic}}
{{#isBasicBearer}}
def info_from_{{name}}(token: str) -> dict:
"""
Check and retrieve authentication information from custom bearer token.
Returned value will be passed in 'token_info' parameter of your operation function, if there is one.
'sub' or 'uid' will be set in 'user' parameter of your operation function, if there is one.
Should return None if auth is invalid or does not allow access to called API.
"""
return {'uid': 'user_id'}
{{/isBasicBearer}}
{{/authMethods}}

View File

@ -0,0 +1,6 @@
coverage>=4.0.3
pytest>=1.3.7
pluggy>=0.3.1
py>=1.4.31
randomize>=0.13
pytest-aiohttp>=0.3.0

View File

@ -0,0 +1,130 @@
import datetime
import typing
from typing import Union
T = typing.TypeVar('T')
Class = typing.Type[T]
def _deserialize(data: Union[dict, list, str], klass: Union[Class, str]) -> Union[dict, list, Class, int, float, str, bool, datetime.date, datetime.datetime]:
"""Deserializes dict, list, str into an object.
:param data: dict, list or str.
:param klass: class literal, or string of class name.
:return: object.
"""
if data is None:
return None
if klass in (int, float, str, bool):
return _deserialize_primitive(data, klass)
elif klass == object:
return _deserialize_object(data)
elif klass == datetime.date:
return deserialize_date(data)
elif klass == datetime.datetime:
return deserialize_datetime(data)
elif type(klass) == typing.GenericMeta:
if klass.__extra__ == list:
return _deserialize_list(data, klass.__args__[0])
if klass.__extra__ == dict:
return _deserialize_dict(data, klass.__args__[1])
else:
return deserialize_model(data, klass)
def _deserialize_primitive(data, klass: Class) -> Union[Class, int, float, str, bool]:
"""Deserializes to primitive type.
:param data: data to deserialize.
:param klass: class literal.
:return: int, float, str, bool.
"""
try:
value = klass(data)
except (UnicodeEncodeError, TypeError):
value = data
return value
def _deserialize_object(value: T) -> T:
"""Return an original value.
:return: object.
"""
return value
def deserialize_date(string: str) -> datetime.date:
"""Deserializes string to date.
:param string: str.
:return: date.
"""
try:
from dateutil.parser import parse
return parse(string).date()
except ImportError:
return string
def deserialize_datetime(string: str) -> datetime.datetime:
"""Deserializes string to datetime.
The string should be in iso8601 datetime format.
:param string: str.
:return: datetime.
"""
try:
from dateutil.parser import parse
return parse(string)
except ImportError:
return string
def deserialize_model(data: Union[dict, list], klass: T) -> T:
"""Deserializes list or dict to model.
:param data: dict, list.
:param klass: class literal.
:return: model object.
"""
instance = klass()
if not instance.openapi_types:
return data
if data is not None and isinstance(data, (list, dict)):
for attr, attr_type in instance.openapi_types.items():
attr_key = instance.attribute_map[attr]
if attr_key in data:
value = data[attr_key]
setattr(instance, attr, _deserialize(value, attr_type))
return instance
def _deserialize_list(data: list, boxed_type) -> list:
"""Deserializes a list and its elements.
:param data: list to deserialize.
:param boxed_type: class literal.
:return: deserialized list.
"""
return [_deserialize(sub_data, boxed_type) for sub_data in data]
def _deserialize_dict(data: dict, boxed_type) -> dict:
"""Deserializes a dict and its elements.
:param data: dict to deserialize.
:param boxed_type: class literal.
:return: deserialized dict.
"""
return {k: _deserialize(v, boxed_type) for k, v in data.items()}

View File

@ -1,6 +1,7 @@
# coding: utf-8
from __future__ import absolute_import
import unittest
from flask import json
from six import BytesIO
@ -14,6 +15,9 @@ class {{#operations}}Test{{classname}}(BaseTestCase):
"""{{classname}} integration test stubs"""
{{#operation}}
{{#vendorExtensions.x-skip-test}}
@unittest.skip("{{reason}}")
{{/vendorExtensions.x-skip-test}}
def test_{{operationId}}(self):
"""Test case for {{{operationId}}}
@ -25,18 +29,21 @@ class {{#operations}}Test{{classname}}(BaseTestCase):
{{#queryParams}}
{{#-first}}query_string = [{{/-first}}{{^-first}} {{/-first}}('{{paramName}}', {{{example}}}){{#hasMore}},{{/hasMore}}{{#-last}}]{{/-last}}
{{/queryParams}}
{{#headerParams}}
{{#-first}}headers = [{{/-first}}{{^-first}} {{/-first}}('{{paramName}}', {{{example}}}){{#hasMore}},{{/hasMore}}{{#-last}}]{{/-last}}
{{/headerParams}}
headers = { {{#vendorExtensions.x-prefered-produce}}
'Accept': '{{mediaType}}',{{/vendorExtensions.x-prefered-produce}}{{#vendorExtensions.x-prefered-consume}}
'Content-Type': '{{mediaType}}',{{/vendorExtensions.x-prefered-consume}}{{#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 = dict({{/-first}}{{^-first}} {{/-first}}{{paramName}}={{{example}}}{{#hasMore}},{{/hasMore}}{{#-last}}){{/-last}}
{{/formParams}}
response = self.client.open(
'{{#contextPath}}{{{.}}}{{/contextPath}}{{{path}}}'{{#pathParams}}{{#-first}}.format({{/-first}}{{paramName}}={{{example}}}{{#hasMore}}, {{/hasMore}}{{^hasMore}}){{/hasMore}}{{/pathParams}},
method='{{httpMethod}}'{{#bodyParam}},
method='{{httpMethod}}',
headers=headers{{#bodyParam}},
data=json.dumps({{paramName}}){{^consumes}},
content_type='application/json'{{/consumes}}{{/bodyParam}}{{#headerParams}}{{#-first}},
headers=headers{{/-first}}{{/headerParams}}{{#formParams}}{{#-first}},
content_type='application/json'{{/consumes}}{{/bodyParam}}{{#formParams}}{{#-first}},
data=data{{/-first}}{{/formParams}}{{#consumes}}{{#-first}},
content_type='{{{mediaType}}}'{{/-first}}{{/consumes}}{{#queryParams}}{{#-first}},
query_string=query_string{{/-first}}{{/queryParams}})
@ -47,5 +54,4 @@ class {{#operations}}Test{{classname}}(BaseTestCase):
{{/operations}}
if __name__ == '__main__':
import unittest
unittest.main()

View File

@ -6,6 +6,13 @@ from datetime import date, datetime # noqa: F401
from typing import List, Dict # noqa: F401
from {{modelPackage}}.base_model_ import Model
{{#models}}
{{#model}}
{{#pyImports}}
{{import}}
{{/pyImports}}
{{/model}}
{{/models}}
from {{packageName}} import util
{{#imports}}

View File

@ -0,0 +1 @@
{{{openapi-yaml}}}

View File

@ -0,0 +1 @@
{{#isString}}str{{/isString}}{{#isInteger}}int{{/isInteger}}{{#isLong}}int{{/isLong}}{{#isFloat}}float{{/isFloat}}{{#isDouble}}float{{/isDouble}}{{#isByteArray}}str{{/isByteArray}}{{#isBinary}}str{{/isBinary}}{{#isBoolean}}bool{{/isBoolean}}{{#isDate}}str{{/isDate}}{{#isDateTime}}str{{/isDateTime}}

View File

@ -1,4 +1,4 @@
connexion == 2.0.0
connexion == 2.0.2
swagger-ui-bundle == 0.0.2
python_dateutil == 2.6.0
{{#supportPython2}}

View File

@ -0,0 +1,90 @@
from typing import List
{{#authMethods}}
{{#isOAuth}}
def info_from_{{name}}(token):
"""
Validate and decode token.
Returned value will be passed in 'token_info' parameter of your operation function, if there is one.
'sub' or 'uid' will be set in 'user' parameter of your operation function, if there is one.
'scope' or 'scopes' will be passed to scope validation function.
:param token Token provided by Authorization header
:type token: str
:return: Decoded token information or None if token is invalid
:rtype: dict | None
"""
return {'scopes': ['read:pets', 'write:pets'], 'uid': 'user_id'}
def validate_scope_{{name}}(required_scopes, token_scopes):
"""
Validate required scopes are included in token scope
:param required_scopes Required scope to access called API
:type required_scopes: List[str]
:param token_scopes Scope present in token
:type token_scopes: List[str]
:return: True if access to called API is allowed
:rtype: bool
"""
return set(required_scopes).issubset(set(token_scopes))
{{/isOAuth}}
{{#isApiKey}}
def info_from_{{name}}(api_key, required_scopes):
"""
Check and retrieve authentication information from api_key.
Returned value will be passed in 'token_info' parameter of your operation function, if there is one.
'sub' or 'uid' will be set in 'user' parameter of your operation function, if there is one.
:param api_key API key provided by Authorization header
:type api_key: str
:param required_scopes Always None. Used for other authentication method
:type required_scopes: None
:return: Information attached to provided api_key or None if api_key is invalid or does not allow access to called API
:rtype: dict | None
"""
return {'uid': 'user_id'}
{{/isApiKey}}
{{#isBasicBasic}}
def info_from_{{name}}(username, password, required_scopes):
"""
Check and retrieve authentication information from basic auth.
Returned value will be passed in 'token_info' parameter of your operation function, if there is one.
'sub' or 'uid' will be set in 'user' parameter of your operation function, if there is one.
:param username login provided by Authorization header
:type username: str
:param password password provided by Authorization header
:type password: str
:param required_scopes Always None. Used for other authentication method
:type required_scopes: None
:return: Information attached to user or None if credentials are invalid or does not allow access to called API
:rtype: dict | None
"""
return {'uid': 'user_id'}
{{/isBasicBasic}}
{{#isBasicBearer}}
def info_from_{{name}}(token):
"""
Check and retrieve authentication information from custom bearer token.
Returned value will be passed in 'token_info' parameter of your operation function, if there is one.
'sub' or 'uid' will be set in 'user' parameter of your operation function, if there is one.
:param token Token provided by Authorization header
:type token: str
:return: Decoded token information or None if token is invalid
:rtype: dict | None
"""
return {'uid': 'user_id'}
{{/isBasicBearer}}
{{/authMethods}}

View File

@ -1,5 +1,5 @@
[tox]
envlist = {{#supportPython2}}py27, {{/supportPython2}}py35
envlist = {{#supportPython2}}py27, {{/supportPython2}}py3
[testenv]
deps=-r{toxinidir}/requirements.txt

View File

@ -1056,6 +1056,9 @@
<module>samples/client/petstore/typescript-angular-v6-provided-in-root</module>
<!--<module>samples/client/petstore/bash</module>-->
<module>samples/server/petstore/rust-server</module>
<module>samples/server/petstore/python-aiohttp</module>
<module>samples/server/petstore/python-flask</module>
<module>samples/server/petstore/python-flask-python2</module>
</modules>
</profile>
<!-- test with JDK8 in CircleCI -->

View File

@ -0,0 +1,18 @@
#!/bin/bash
REQUIREMENTS_OUT=requirements.txt.log
VENV=.venv
clean:
rm -rf $(REQUIREMENTS_OUT)
rm -rf $(VENV)
rm -rf .pytest_cache
rm -rf .coverage
find . -name "*.py[oc]" -delete
find . -name "__pycache__" -delete
test: clean
bash ./test_python3.sh
test-all: clean
bash ./test_python3.sh

View File

@ -0,0 +1,46 @@
# OpenAPI generated server
## Overview
This server was generated by the [OpenAPI Generator](https://openapi-generator.tech) project. By using the
[OpenAPI-Spec](https://openapis.org) from a remote server, you can easily generate a server stub. This
is an example of building a OpenAPI-enabled aiohtpp server.
This example uses the [Connexion](https://github.com/zalando/connexion) library on top of aiohtpp.
## Requirements
Python 3.5.2+
## Usage
To run the server, please execute the following from the root directory:
```
pip3 install -r requirements.txt
python3 -m openapi_server
```
and open your browser to here:
```
http://localhost:8080/v2/ui/
```
Your OpenAPI definition lives here:
```
http://localhost:8080/v2/openapi.json
```
To launch the integration tests, use pytest:
```
sudo pip install -r test-requirements.txt
pytest
```
## Prevent file overriding
After first generation, add edited files to _.openapi-generator-ignore_ to prevent generator to overwrite them. Typically:
```
server/controllers/*
test/*
*.txt
```

View File

@ -0,0 +1,6 @@
#!/usr/bin/env python3
from . import main
if __name__ == '__main__':
main()

View File

@ -0,0 +1,114 @@
from typing import List, Dict
from aiohttp import web
from openapi_server.models.api_response import ApiResponse
from openapi_server.models.pet import Pet
from openapi_server import util
async def add_pet(request: web.Request, body) -> web.Response:
"""Add a new pet to the store
:param body: Pet object that needs to be added to the store
:type body: dict | bytes
"""
body = Pet.from_dict(body)
return web.Response(status=200)
async def delete_pet(request: web.Request, pet_id, api_key=None) -> web.Response:
"""Deletes a pet
:param pet_id: Pet id to delete
:type pet_id: int
:param api_key:
:type api_key: str
"""
return web.Response(status=200)
async def find_pets_by_status(request: web.Request, status) -> web.Response:
"""Finds Pets by status
Multiple status values can be provided with comma separated strings
:param status: Status values that need to be considered for filter
:type status: List[str]
"""
return web.Response(status=200)
async def find_pets_by_tags(request: web.Request, tags) -> web.Response:
"""Finds Pets by tags
Multiple tags can be provided with comma separated strings. Use tag1, tag2, tag3 for testing.
:param tags: Tags to filter by
:type tags: List[str]
"""
return web.Response(status=200)
async def get_pet_by_id(request: web.Request, pet_id) -> web.Response:
"""Find pet by ID
Returns a single pet
:param pet_id: ID of pet to return
:type pet_id: int
"""
return web.Response(status=200)
async def update_pet(request: web.Request, body) -> web.Response:
"""Update an existing pet
:param body: Pet object that needs to be added to the store
:type body: dict | bytes
"""
body = Pet.from_dict(body)
return web.Response(status=200)
async def update_pet_with_form(request: web.Request, pet_id, name=None, status=None) -> web.Response:
"""Updates a pet in the store with form data
:param pet_id: ID of pet that needs to be updated
:type pet_id: int
:param name: Updated name of the pet
:type name: str
:param status: Updated status of the pet
:type status: str
"""
return web.Response(status=200)
async def upload_file(request: web.Request, pet_id, additional_metadata=None, file=None) -> web.Response:
"""uploads an image
:param pet_id: ID of pet to update
:type pet_id: int
:param additional_metadata: Additional data to pass to server
:type additional_metadata: str
:param file: file to upload
:type file: str
"""
return web.Response(status=200)

View File

@ -0,0 +1,29 @@
from typing import List
def info_from_api_key(api_key: str, required_scopes: None) -> dict:
"""
Check and retrieve authentication information from api_key.
Returned value will be passed in 'token_info' parameter of your operation function, if there is one.
'sub' or 'uid' will be set in 'user' parameter of your operation function, if there is one.
Should return None if api_key is invalid or does not allow access to called API.
"""
return {'uid': 'user_id'}
def info_from_petstore_auth(token: str) -> dict:
"""
Validate and decode token.
Returned value will be passed in 'token_info' parameter of your operation function, if there is one.
'sub' or 'uid' will be set in 'user' parameter of your operation function, if there is one.
'scope' or 'scopes' will be passed to scope validation function.
Should return None if token is invalid or does not allow access to called API.
"""
return {'scopes': ['read:pets', 'write:pets'], 'uid': 'user_id'}
def validate_scope_petstore_auth(required_scopes: List[str], token_scopes: List[str]) -> bool:
""" Validate required scopes are included in token scope """
return set(required_scopes).issubset(set(token_scopes))

View File

@ -0,0 +1,52 @@
from typing import List, Dict
from aiohttp import web
from openapi_server.models.order import Order
from openapi_server import util
async def delete_order(request: web.Request, order_id) -> web.Response:
"""Delete purchase order by ID
For valid response try integer IDs with value &lt; 1000. Anything above 1000 or nonintegers will generate API errors
:param order_id: ID of the order that needs to be deleted
:type order_id: str
"""
return web.Response(status=200)
async def get_inventory(request: web.Request, ) -> web.Response:
"""Returns pet inventories by status
Returns a map of status codes to quantities
"""
return web.Response(status=200)
async def get_order_by_id(request: web.Request, order_id) -> web.Response:
"""Find purchase order by ID
For valid response try integer IDs with value &lt;&#x3D; 5 or &gt; 10. Other values will generated exceptions
:param order_id: ID of pet that needs to be fetched
:type order_id: int
"""
return web.Response(status=200)
async def place_order(request: web.Request, body) -> web.Response:
"""Place an order for a pet
:param body: order placed for purchasing the pet
:type body: dict | bytes
"""
body = Order.from_dict(body)
return web.Response(status=200)

View File

@ -0,0 +1,107 @@
from typing import List, Dict
from aiohttp import web
from openapi_server.models.user import User
from openapi_server import util
async def create_user(request: web.Request, body) -> web.Response:
"""Create user
This can only be done by the logged in user.
:param body: Created user object
:type body: dict | bytes
"""
body = User.from_dict(body)
return web.Response(status=200)
async def create_users_with_array_input(request: web.Request, body) -> web.Response:
"""Creates list of users with given input array
:param body: List of user object
:type body: list | bytes
"""
body = [User.from_dict(d) for d in body]
return web.Response(status=200)
async def create_users_with_list_input(request: web.Request, body) -> web.Response:
"""Creates list of users with given input array
:param body: List of user object
:type body: list | bytes
"""
body = [User.from_dict(d) for d in body]
return web.Response(status=200)
async def delete_user(request: web.Request, username) -> web.Response:
"""Delete user
This can only be done by the logged in user.
:param username: The name that needs to be deleted
:type username: str
"""
return web.Response(status=200)
async def get_user_by_name(request: web.Request, username) -> web.Response:
"""Get user by user name
:param username: The name that needs to be fetched. Use user1 for testing.
:type username: str
"""
return web.Response(status=200)
async def login_user(request: web.Request, username, password) -> web.Response:
"""Logs user into the system
:param username: The user name for login
:type username: str
:param password: The password for login in clear text
:type password: str
"""
return web.Response(status=200)
async def logout_user(request: web.Request, ) -> web.Response:
"""Logs out current logged in user session
"""
return web.Response(status=200)
async def update_user(request: web.Request, username, body) -> web.Response:
"""Updated user
This can only be done by the logged in user.
:param username: name that need to be deleted
:type username: str
:param body: Updated user object
:type body: dict | bytes
"""
body = User.from_dict(body)
return web.Response(status=200)

View File

@ -0,0 +1,9 @@
# coding: utf-8
# import models into model package
from openapi_server.models.api_response import ApiResponse
from openapi_server.models.category import Category
from openapi_server.models.order import Order
from openapi_server.models.pet import Pet
from openapi_server.models.tag import Tag
from openapi_server.models.user import User

View File

@ -0,0 +1,110 @@
# coding: utf-8
from datetime import date, datetime
from typing import List, Dict, Type
from openapi_server.models.base_model_ import Model
from openapi_server import util
class ApiResponse(Model):
"""NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
Do not edit the class manually.
"""
def __init__(self, code: int=None, type: str=None, message: str=None):
"""ApiResponse - a model defined in OpenAPI
:param code: The code of this ApiResponse.
:param type: The type of this ApiResponse.
:param message: The message of this ApiResponse.
"""
self.openapi_types = {
'code': int,
'type': str,
'message': str
}
self.attribute_map = {
'code': 'code',
'type': 'type',
'message': 'message'
}
self._code = code
self._type = type
self._message = message
@classmethod
def from_dict(cls, dikt: dict) -> 'ApiResponse':
"""Returns the dict as a model
:param dikt: A dict.
:return: The ApiResponse of this ApiResponse.
"""
return util.deserialize_model(dikt, cls)
@property
def code(self):
"""Gets the code of this ApiResponse.
:return: The code of this ApiResponse.
:rtype: int
"""
return self._code
@code.setter
def code(self, code):
"""Sets the code of this ApiResponse.
:param code: The code of this ApiResponse.
:type code: int
"""
self._code = code
@property
def type(self):
"""Gets the type of this ApiResponse.
:return: The type of this ApiResponse.
:rtype: str
"""
return self._type
@type.setter
def type(self, type):
"""Sets the type of this ApiResponse.
:param type: The type of this ApiResponse.
:type type: str
"""
self._type = type
@property
def message(self):
"""Gets the message of this ApiResponse.
:return: The message of this ApiResponse.
:rtype: str
"""
return self._message
@message.setter
def message(self, message):
"""Sets the message of this ApiResponse.
:param message: The message of this ApiResponse.
:type message: str
"""
self._message = message

View File

@ -0,0 +1,66 @@
import pprint
import typing
from openapi_server import util
T = typing.TypeVar('T')
class Model(object):
# openapiTypes: The key is attribute name and the
# value is attribute type.
openapi_types = {}
# attributeMap: The key is attribute name and the
# value is json key in definition.
attribute_map = {}
@classmethod
def from_dict(cls: T, dikt: dict) -> T:
"""Returns the dict as a model"""
return util.deserialize_model(dikt, cls)
def to_dict(self) -> dict:
"""Returns the model properties as a dict
"""
result = {}
for attr_key, json_key in self.attribute_map.items():
value = getattr(self, attr_key)
if value is None:
continue
if isinstance(value, list):
result[json_key] = list(map(
lambda x: x.to_dict() if hasattr(x, "to_dict") else x,
value
))
elif hasattr(value, "to_dict"):
result[json_key] = value.to_dict()
elif isinstance(value, dict):
result[json_key] = dict(map(
lambda item: (item[0], item[1].to_dict())
if hasattr(item[1], "to_dict") else item,
value.items()
))
else:
result[json_key] = value
return result
def to_str(self) -> str:
"""Returns the string representation of the model
"""
return pprint.pformat(self.to_dict())
def __repr__(self):
"""For `print` and `pprint`"""
return self.to_str()
def __eq__(self, other):
"""Returns true if both objects are equal"""
return self.__dict__ == other.__dict__
def __ne__(self, other):
"""Returns true if both objects are not equal"""
return not self == other

View File

@ -0,0 +1,85 @@
# coding: utf-8
from datetime import date, datetime
from typing import List, Dict, Type
from openapi_server.models.base_model_ import Model
from openapi_server import util
class Category(Model):
"""NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
Do not edit the class manually.
"""
def __init__(self, id: int=None, name: str=None):
"""Category - a model defined in OpenAPI
:param id: The id of this Category.
:param name: The name of this Category.
"""
self.openapi_types = {
'id': int,
'name': str
}
self.attribute_map = {
'id': 'id',
'name': 'name'
}
self._id = id
self._name = name
@classmethod
def from_dict(cls, dikt: dict) -> 'Category':
"""Returns the dict as a model
:param dikt: A dict.
:return: The Category of this Category.
"""
return util.deserialize_model(dikt, cls)
@property
def id(self):
"""Gets the id of this Category.
:return: The id of this Category.
:rtype: int
"""
return self._id
@id.setter
def id(self, id):
"""Sets the id of this Category.
:param id: The id of this Category.
:type id: int
"""
self._id = id
@property
def name(self):
"""Gets the name of this Category.
:return: The name of this Category.
:rtype: str
"""
return self._name
@name.setter
def name(self, name):
"""Sets the name of this Category.
:param name: The name of this Category.
:type name: str
"""
self._name = name

View File

@ -0,0 +1,193 @@
# coding: utf-8
from datetime import date, datetime
from typing import List, Dict, Type
from openapi_server.models.base_model_ import Model
from openapi_server import util
class Order(Model):
"""NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
Do not edit the class manually.
"""
def __init__(self, id: int=None, pet_id: int=None, quantity: int=None, ship_date: datetime=None, status: str=None, complete: bool=False):
"""Order - a model defined in OpenAPI
:param id: The id of this Order.
:param pet_id: The pet_id of this Order.
:param quantity: The quantity of this Order.
:param ship_date: The ship_date of this Order.
:param status: The status of this Order.
:param complete: The complete of this Order.
"""
self.openapi_types = {
'id': int,
'pet_id': int,
'quantity': int,
'ship_date': datetime,
'status': str,
'complete': bool
}
self.attribute_map = {
'id': 'id',
'pet_id': 'petId',
'quantity': 'quantity',
'ship_date': 'shipDate',
'status': 'status',
'complete': 'complete'
}
self._id = id
self._pet_id = pet_id
self._quantity = quantity
self._ship_date = ship_date
self._status = status
self._complete = complete
@classmethod
def from_dict(cls, dikt: dict) -> 'Order':
"""Returns the dict as a model
:param dikt: A dict.
:return: The Order of this Order.
"""
return util.deserialize_model(dikt, cls)
@property
def id(self):
"""Gets the id of this Order.
:return: The id of this Order.
:rtype: int
"""
return self._id
@id.setter
def id(self, id):
"""Sets the id of this Order.
:param id: The id of this Order.
:type id: int
"""
self._id = id
@property
def pet_id(self):
"""Gets the pet_id of this Order.
:return: The pet_id of this Order.
:rtype: int
"""
return self._pet_id
@pet_id.setter
def pet_id(self, pet_id):
"""Sets the pet_id of this Order.
:param pet_id: The pet_id of this Order.
:type pet_id: int
"""
self._pet_id = pet_id
@property
def quantity(self):
"""Gets the quantity of this Order.
:return: The quantity of this Order.
:rtype: int
"""
return self._quantity
@quantity.setter
def quantity(self, quantity):
"""Sets the quantity of this Order.
:param quantity: The quantity of this Order.
:type quantity: int
"""
self._quantity = quantity
@property
def ship_date(self):
"""Gets the ship_date of this Order.
:return: The ship_date of this Order.
:rtype: datetime
"""
return self._ship_date
@ship_date.setter
def ship_date(self, ship_date):
"""Sets the ship_date of this Order.
:param ship_date: The ship_date of this Order.
:type ship_date: datetime
"""
self._ship_date = ship_date
@property
def status(self):
"""Gets the status of this Order.
Order Status
:return: The status of this Order.
:rtype: str
"""
return self._status
@status.setter
def status(self, status):
"""Sets the status of this Order.
Order Status
:param status: The status of this Order.
:type status: str
"""
allowed_values = ["placed", "approved", "delivered"]
if status not in allowed_values:
raise ValueError(
"Invalid value for `status` ({0}), must be one of {1}"
.format(status, allowed_values)
)
self._status = status
@property
def complete(self):
"""Gets the complete of this Order.
:return: The complete of this Order.
:rtype: bool
"""
return self._complete
@complete.setter
def complete(self, complete):
"""Sets the complete of this Order.
:param complete: The complete of this Order.
:type complete: bool
"""
self._complete = complete

View File

@ -0,0 +1,199 @@
# coding: utf-8
from datetime import date, datetime
from typing import List, Dict, Type
from openapi_server.models.base_model_ import Model
from openapi_server.models.category import Category
from openapi_server.models.tag import Tag
from openapi_server import util
class Pet(Model):
"""NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
Do not edit the class manually.
"""
def __init__(self, id: int=None, category: Category=None, name: str=None, photo_urls: List[str]=None, tags: List[Tag]=None, status: str=None):
"""Pet - a model defined in OpenAPI
:param id: The id of this Pet.
:param category: The category of this Pet.
:param name: The name of this Pet.
:param photo_urls: The photo_urls of this Pet.
:param tags: The tags of this Pet.
:param status: The status of this Pet.
"""
self.openapi_types = {
'id': int,
'category': Category,
'name': str,
'photo_urls': List[str],
'tags': List[Tag],
'status': str
}
self.attribute_map = {
'id': 'id',
'category': 'category',
'name': 'name',
'photo_urls': 'photoUrls',
'tags': 'tags',
'status': 'status'
}
self._id = id
self._category = category
self._name = name
self._photo_urls = photo_urls
self._tags = tags
self._status = status
@classmethod
def from_dict(cls, dikt: dict) -> 'Pet':
"""Returns the dict as a model
:param dikt: A dict.
:return: The Pet of this Pet.
"""
return util.deserialize_model(dikt, cls)
@property
def id(self):
"""Gets the id of this Pet.
:return: The id of this Pet.
:rtype: int
"""
return self._id
@id.setter
def id(self, id):
"""Sets the id of this Pet.
:param id: The id of this Pet.
:type id: int
"""
self._id = id
@property
def category(self):
"""Gets the category of this Pet.
:return: The category of this Pet.
:rtype: Category
"""
return self._category
@category.setter
def category(self, category):
"""Sets the category of this Pet.
:param category: The category of this Pet.
:type category: Category
"""
self._category = category
@property
def name(self):
"""Gets the name of this Pet.
:return: The name of this Pet.
:rtype: str
"""
return self._name
@name.setter
def name(self, name):
"""Sets the name of this Pet.
:param name: The name of this Pet.
:type name: str
"""
if name is None:
raise ValueError("Invalid value for `name`, must not be `None`")
self._name = name
@property
def photo_urls(self):
"""Gets the photo_urls of this Pet.
:return: The photo_urls of this Pet.
:rtype: List[str]
"""
return self._photo_urls
@photo_urls.setter
def photo_urls(self, photo_urls):
"""Sets the photo_urls of this Pet.
:param photo_urls: The photo_urls of this Pet.
:type photo_urls: List[str]
"""
if photo_urls is None:
raise ValueError("Invalid value for `photo_urls`, must not be `None`")
self._photo_urls = photo_urls
@property
def tags(self):
"""Gets the tags of this Pet.
:return: The tags of this Pet.
:rtype: List[Tag]
"""
return self._tags
@tags.setter
def tags(self, tags):
"""Sets the tags of this Pet.
:param tags: The tags of this Pet.
:type tags: List[Tag]
"""
self._tags = tags
@property
def status(self):
"""Gets the status of this Pet.
pet status in the store
:return: The status of this Pet.
:rtype: str
"""
return self._status
@status.setter
def status(self, status):
"""Sets the status of this Pet.
pet status in the store
:param status: The status of this Pet.
:type status: str
"""
allowed_values = ["available", "pending", "sold"]
if status not in allowed_values:
raise ValueError(
"Invalid value for `status` ({0}), must be one of {1}"
.format(status, allowed_values)
)
self._status = status

View File

@ -0,0 +1,85 @@
# coding: utf-8
from datetime import date, datetime
from typing import List, Dict, Type
from openapi_server.models.base_model_ import Model
from openapi_server import util
class Tag(Model):
"""NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
Do not edit the class manually.
"""
def __init__(self, id: int=None, name: str=None):
"""Tag - a model defined in OpenAPI
:param id: The id of this Tag.
:param name: The name of this Tag.
"""
self.openapi_types = {
'id': int,
'name': str
}
self.attribute_map = {
'id': 'id',
'name': 'name'
}
self._id = id
self._name = name
@classmethod
def from_dict(cls, dikt: dict) -> 'Tag':
"""Returns the dict as a model
:param dikt: A dict.
:return: The Tag of this Tag.
"""
return util.deserialize_model(dikt, cls)
@property
def id(self):
"""Gets the id of this Tag.
:return: The id of this Tag.
:rtype: int
"""
return self._id
@id.setter
def id(self, id):
"""Sets the id of this Tag.
:param id: The id of this Tag.
:type id: int
"""
self._id = id
@property
def name(self):
"""Gets the name of this Tag.
:return: The name of this Tag.
:rtype: str
"""
return self._name
@name.setter
def name(self, name):
"""Sets the name of this Tag.
:param name: The name of this Tag.
:type name: str
"""
self._name = name

View File

@ -0,0 +1,237 @@
# coding: utf-8
from datetime import date, datetime
from typing import List, Dict, Type
from openapi_server.models.base_model_ import Model
from openapi_server import util
class User(Model):
"""NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
Do not edit the class manually.
"""
def __init__(self, id: int=None, username: str=None, first_name: str=None, last_name: str=None, email: str=None, password: str=None, phone: str=None, user_status: int=None):
"""User - a model defined in OpenAPI
:param id: The id of this User.
:param username: The username of this User.
:param first_name: The first_name of this User.
:param last_name: The last_name of this User.
:param email: The email of this User.
:param password: The password of this User.
:param phone: The phone of this User.
:param user_status: The user_status of this User.
"""
self.openapi_types = {
'id': int,
'username': str,
'first_name': str,
'last_name': str,
'email': str,
'password': str,
'phone': str,
'user_status': int
}
self.attribute_map = {
'id': 'id',
'username': 'username',
'first_name': 'firstName',
'last_name': 'lastName',
'email': 'email',
'password': 'password',
'phone': 'phone',
'user_status': 'userStatus'
}
self._id = id
self._username = username
self._first_name = first_name
self._last_name = last_name
self._email = email
self._password = password
self._phone = phone
self._user_status = user_status
@classmethod
def from_dict(cls, dikt: dict) -> 'User':
"""Returns the dict as a model
:param dikt: A dict.
:return: The User of this User.
"""
return util.deserialize_model(dikt, cls)
@property
def id(self):
"""Gets the id of this User.
:return: The id of this User.
:rtype: int
"""
return self._id
@id.setter
def id(self, id):
"""Sets the id of this User.
:param id: The id of this User.
:type id: int
"""
self._id = id
@property
def username(self):
"""Gets the username of this User.
:return: The username of this User.
:rtype: str
"""
return self._username
@username.setter
def username(self, username):
"""Sets the username of this User.
:param username: The username of this User.
:type username: str
"""
self._username = username
@property
def first_name(self):
"""Gets the first_name of this User.
:return: The first_name of this User.
:rtype: str
"""
return self._first_name
@first_name.setter
def first_name(self, first_name):
"""Sets the first_name of this User.
:param first_name: The first_name of this User.
:type first_name: str
"""
self._first_name = first_name
@property
def last_name(self):
"""Gets the last_name of this User.
:return: The last_name of this User.
:rtype: str
"""
return self._last_name
@last_name.setter
def last_name(self, last_name):
"""Sets the last_name of this User.
:param last_name: The last_name of this User.
:type last_name: str
"""
self._last_name = last_name
@property
def email(self):
"""Gets the email of this User.
:return: The email of this User.
:rtype: str
"""
return self._email
@email.setter
def email(self, email):
"""Sets the email of this User.
:param email: The email of this User.
:type email: str
"""
self._email = email
@property
def password(self):
"""Gets the password of this User.
:return: The password of this User.
:rtype: str
"""
return self._password
@password.setter
def password(self, password):
"""Sets the password of this User.
:param password: The password of this User.
:type password: str
"""
self._password = password
@property
def phone(self):
"""Gets the phone of this User.
:return: The phone of this User.
:rtype: str
"""
return self._phone
@phone.setter
def phone(self, phone):
"""Sets the phone of this User.
:param phone: The phone of this User.
:type phone: str
"""
self._phone = phone
@property
def user_status(self):
"""Gets the user_status of this User.
User Status
:return: The user_status of this User.
:rtype: int
"""
return self._user_status
@user_status.setter
def user_status(self, user_status):
"""Sets the user_status of this User.
User Status
:param user_status: The user_status of this User.
:type user_status: int
"""
self._user_status = user_status

View File

@ -0,0 +1,792 @@
openapi: 3.0.1
info:
description: This is a sample server Petstore server. For this sample, you can use the api key `special-key` to test the authorization filters.
license:
name: Apache-2.0
url: http://www.apache.org/licenses/LICENSE-2.0.html
title: OpenAPI Petstore
version: 1.0.0
servers:
- url: http://petstore.swagger.io/v2
tags:
- description: Everything about your Pets
name: pet
- description: Access to Petstore orders
name: store
- description: Operations about user
name: user
paths:
/pet:
post:
operationId: add_pet
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/Pet'
application/xml:
schema:
$ref: '#/components/schemas/Pet'
description: Pet object that needs to be added to the store
required: true
x-body-name: body
responses:
405:
content: {}
description: Invalid input
security:
- petstore_auth:
- write:pets
- read:pets
summary: Add a new pet to the store
tags:
- pet
x-codegen-request-body-name: body
x-openapi-router-controller: openapi_server.controllers.pet_controller
put:
operationId: update_pet
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/Pet'
application/xml:
schema:
$ref: '#/components/schemas/Pet'
description: Pet object that needs to be added to the store
required: true
x-body-name: body
responses:
400:
content: {}
description: Invalid ID supplied
404:
content: {}
description: Pet not found
405:
content: {}
description: Validation exception
security:
- petstore_auth:
- write:pets
- read:pets
summary: Update an existing pet
tags:
- pet
x-codegen-request-body-name: body
x-openapi-router-controller: openapi_server.controllers.pet_controller
/pet/findByStatus:
get:
description: Multiple status values can be provided with comma separated strings
operationId: find_pets_by_status
parameters:
- description: Status values that need to be considered for filter
explode: false
in: query
name: status
required: true
schema:
items:
default: available
enum:
- available
- pending
- sold
type: string
type: array
style: form
responses:
200:
content:
application/xml:
schema:
items:
$ref: '#/components/schemas/Pet'
type: array
application/json:
schema:
items:
$ref: '#/components/schemas/Pet'
type: array
description: successful operation
400:
content: {}
description: Invalid status value
security:
- petstore_auth:
- write:pets
- read:pets
summary: Finds Pets by status
tags:
- pet
x-openapi-router-controller: openapi_server.controllers.pet_controller
/pet/findByTags:
get:
deprecated: true
description: Multiple tags can be provided with comma separated strings. Use tag1, tag2, tag3 for testing.
operationId: find_pets_by_tags
parameters:
- description: Tags to filter by
explode: false
in: query
name: tags
required: true
schema:
items:
type: string
type: array
style: form
responses:
200:
content:
application/xml:
schema:
items:
$ref: '#/components/schemas/Pet'
type: array
application/json:
schema:
items:
$ref: '#/components/schemas/Pet'
type: array
description: successful operation
400:
content: {}
description: Invalid tag value
security:
- petstore_auth:
- write:pets
- read:pets
summary: Finds Pets by tags
tags:
- pet
x-openapi-router-controller: openapi_server.controllers.pet_controller
/pet/{pet_id}:
delete:
operationId: delete_pet
parameters:
- in: header
name: api_key
schema:
type: string
- description: Pet id to delete
in: path
name: pet_id
required: true
schema:
format: int64
type: integer
responses:
400:
content: {}
description: Invalid pet value
security:
- petstore_auth:
- write:pets
- read:pets
summary: Deletes a pet
tags:
- pet
x-openapi-router-controller: openapi_server.controllers.pet_controller
get:
description: Returns a single pet
operationId: get_pet_by_id
parameters:
- description: ID of pet to return
in: path
name: pet_id
required: true
schema:
format: int64
type: integer
responses:
200:
content:
application/xml:
schema:
$ref: '#/components/schemas/Pet'
application/json:
schema:
$ref: '#/components/schemas/Pet'
description: successful operation
400:
content: {}
description: Invalid ID supplied
404:
content: {}
description: Pet not found
security:
- api_key: []
summary: Find pet by ID
tags:
- pet
x-openapi-router-controller: openapi_server.controllers.pet_controller
post:
operationId: update_pet_with_form
parameters:
- description: ID of pet that needs to be updated
in: path
name: pet_id
required: true
schema:
format: int64
type: integer
requestBody:
content:
application/x-www-form-urlencoded:
schema:
properties:
name:
description: Updated name of the pet
type: string
status:
description: Updated status of the pet
type: string
x-body-name: body
responses:
405:
content: {}
description: Invalid input
security:
- petstore_auth:
- write:pets
- read:pets
summary: Updates a pet in the store with form data
tags:
- pet
x-openapi-router-controller: openapi_server.controllers.pet_controller
x-codegen-request-body-name: body
/pet/{pet_id}/uploadImage:
post:
operationId: upload_file
parameters:
- description: ID of pet to update
in: path
name: pet_id
required: true
schema:
format: int64
type: integer
requestBody:
content:
multipart/form-data:
schema:
properties:
additionalMetadata:
description: Additional data to pass to server
type: string
file:
description: file to upload
format: binary
type: string
x-body-name: body
responses:
200:
content:
application/json:
schema:
$ref: '#/components/schemas/ApiResponse'
description: successful operation
security:
- petstore_auth:
- write:pets
- read:pets
summary: uploads an image
tags:
- pet
x-openapi-router-controller: openapi_server.controllers.pet_controller
x-codegen-request-body-name: body
/store/inventory:
get:
description: Returns a map of status codes to quantities
operationId: get_inventory
responses:
200:
content:
application/json:
schema:
additionalProperties:
format: int32
type: integer
type: object
description: successful operation
security:
- api_key: []
summary: Returns pet inventories by status
tags:
- store
x-openapi-router-controller: openapi_server.controllers.store_controller
/store/order:
post:
operationId: place_order
requestBody:
content:
'*/*':
schema:
$ref: '#/components/schemas/Order'
description: order placed for purchasing the pet
required: true
x-body-name: body
responses:
200:
content:
application/xml:
schema:
$ref: '#/components/schemas/Order'
application/json:
schema:
$ref: '#/components/schemas/Order'
description: successful operation
400:
content: {}
description: Invalid Order
summary: Place an order for a pet
tags:
- store
x-codegen-request-body-name: body
x-openapi-router-controller: openapi_server.controllers.store_controller
/store/order/{order_id}:
delete:
description: For valid response try integer IDs with value < 1000. Anything above 1000 or nonintegers will generate API errors
operationId: delete_order
parameters:
- description: ID of the order that needs to be deleted
in: path
name: order_id
required: true
schema:
type: string
responses:
400:
content: {}
description: Invalid ID supplied
404:
content: {}
description: Order not found
summary: Delete purchase order by ID
tags:
- store
x-openapi-router-controller: openapi_server.controllers.store_controller
get:
description: For valid response try integer IDs with value <= 5 or > 10. Other values will generated exceptions
operationId: get_order_by_id
parameters:
- description: ID of pet that needs to be fetched
in: path
name: order_id
required: true
schema:
format: int64
maximum: 5
minimum: 1
type: integer
responses:
200:
content:
application/xml:
schema:
$ref: '#/components/schemas/Order'
application/json:
schema:
$ref: '#/components/schemas/Order'
description: successful operation
400:
content: {}
description: Invalid ID supplied
404:
content: {}
description: Order not found
summary: Find purchase order by ID
tags:
- store
x-openapi-router-controller: openapi_server.controllers.store_controller
/user:
post:
description: This can only be done by the logged in user.
operationId: create_user
requestBody:
content:
'*/*':
schema:
$ref: '#/components/schemas/User'
description: Created user object
required: true
x-body-name: body
responses:
default:
content: {}
description: successful operation
summary: Create user
tags:
- user
x-codegen-request-body-name: body
x-openapi-router-controller: openapi_server.controllers.user_controller
/user/createWithArray:
post:
operationId: create_users_with_array_input
requestBody:
content:
'*/*':
schema:
items:
$ref: '#/components/schemas/User'
type: array
description: List of user object
required: true
x-body-name: body
responses:
default:
content: {}
description: successful operation
summary: Creates list of users with given input array
tags:
- user
x-codegen-request-body-name: body
x-openapi-router-controller: openapi_server.controllers.user_controller
/user/createWithList:
post:
operationId: create_users_with_list_input
requestBody:
content:
'*/*':
schema:
items:
$ref: '#/components/schemas/User'
type: array
description: List of user object
required: true
x-body-name: body
responses:
default:
content: {}
description: successful operation
summary: Creates list of users with given input array
tags:
- user
x-codegen-request-body-name: body
x-openapi-router-controller: openapi_server.controllers.user_controller
/user/login:
get:
operationId: login_user
parameters:
- description: The user name for login
in: query
name: username
required: true
schema:
type: string
- description: The password for login in clear text
in: query
name: password
required: true
schema:
type: string
responses:
200:
content:
application/xml:
schema:
type: string
application/json:
schema:
type: string
description: successful operation
headers:
X-Rate-Limit:
description: calls per hour allowed by the user
schema:
format: int32
type: integer
X-Expires-After:
description: date in UTC when toekn expires
schema:
format: date-time
type: string
400:
content: {}
description: Invalid username/password supplied
summary: Logs user into the system
tags:
- user
x-openapi-router-controller: openapi_server.controllers.user_controller
/user/logout:
get:
operationId: logout_user
responses:
default:
content: {}
description: successful operation
summary: Logs out current logged in user session
tags:
- user
x-openapi-router-controller: openapi_server.controllers.user_controller
/user/{username}:
delete:
description: This can only be done by the logged in user.
operationId: delete_user
parameters:
- description: The name that needs to be deleted
in: path
name: username
required: true
schema:
type: string
responses:
400:
content: {}
description: Invalid username supplied
404:
content: {}
description: User not found
summary: Delete user
tags:
- user
x-openapi-router-controller: openapi_server.controllers.user_controller
get:
operationId: get_user_by_name
parameters:
- description: The name that needs to be fetched. Use user1 for testing.
in: path
name: username
required: true
schema:
type: string
responses:
200:
content:
application/xml:
schema:
$ref: '#/components/schemas/User'
application/json:
schema:
$ref: '#/components/schemas/User'
description: successful operation
400:
content: {}
description: Invalid username supplied
404:
content: {}
description: User not found
summary: Get user by user name
tags:
- user
x-openapi-router-controller: openapi_server.controllers.user_controller
put:
description: This can only be done by the logged in user.
operationId: update_user
parameters:
- description: name that need to be deleted
in: path
name: username
required: true
schema:
type: string
requestBody:
content:
'*/*':
schema:
$ref: '#/components/schemas/User'
description: Updated user object
required: true
x-body-name: body
responses:
400:
content: {}
description: Invalid user supplied
404:
content: {}
description: User not found
summary: Updated user
tags:
- user
x-codegen-request-body-name: body
x-openapi-router-controller: openapi_server.controllers.user_controller
components:
schemas:
Order:
description: An order for a pets from the pet store
example:
petId: 6
quantity: 1
id: 0
shipDate: 2000-01-23T04:56:07.000+00:00
complete: false
status: placed
properties:
id:
format: int64
type: integer
petId:
format: int64
type: integer
quantity:
format: int32
type: integer
shipDate:
format: date-time
type: string
status:
description: Order Status
enum:
- placed
- approved
- delivered
type: string
complete:
default: false
type: boolean
title: Pet Order
type: object
xml:
name: Order
Category:
description: A category for a pet
example:
name: name
id: 6
properties:
id:
format: int64
type: integer
name:
type: string
title: Pet category
type: object
xml:
name: Category
User:
description: A User who is purchasing from the pet store
example:
firstName: firstName
lastName: lastName
password: password
userStatus: 6
phone: phone
id: 0
email: email
username: username
properties:
id:
format: int64
type: integer
username:
type: string
firstName:
type: string
lastName:
type: string
email:
type: string
password:
type: string
phone:
type: string
userStatus:
description: User Status
format: int32
type: integer
title: a User
type: object
xml:
name: User
Tag:
description: A tag for a pet
example:
name: name
id: 1
properties:
id:
format: int64
type: integer
name:
type: string
title: Pet Tag
type: object
xml:
name: Tag
Pet:
description: A pet for sale in the pet store
example:
photoUrls:
- photoUrls
- photoUrls
name: doggie
id: 0
category:
name: name
id: 6
tags:
- name: name
id: 1
- name: name
id: 1
status: available
properties:
id:
format: int64
type: integer
category:
$ref: '#/components/schemas/Category'
name:
example: doggie
type: string
photoUrls:
items:
type: string
type: array
xml:
name: photoUrl
wrapped: true
tags:
items:
$ref: '#/components/schemas/Tag'
type: array
xml:
name: tag
wrapped: true
status:
description: pet status in the store
enum:
- available
- pending
- sold
type: string
required:
- name
- photoUrls
title: a Pet
type: object
xml:
name: Pet
ApiResponse:
description: Describes the result of uploading an image resource
example:
code: 0
type: type
message: message
properties:
code:
format: int32
type: integer
type:
type: string
message:
type: string
title: An uploaded response
type: object
securitySchemes:
petstore_auth:
flows:
implicit:
authorizationUrl: http://petstore.swagger.io/api/oauth/dialog
scopes:
write:pets: modify pets in your account
read:pets: read your pets
type: oauth2
x-tokenInfoFunc: openapi_server.controllers.security_controller_.info_from_petstore_auth
x-scopeValidateFunc: openapi_server.controllers.security_controller_.validate_scope_petstore_auth
api_key:
in: header
name: api_key
type: apiKey
x-apikeyInfoFunc: openapi_server.controllers.security_controller_.info_from_api_key

View File

@ -0,0 +1,130 @@
import datetime
import typing
from typing import Union
T = typing.TypeVar('T')
Class = typing.Type[T]
def _deserialize(data: Union[dict, list, str], klass: Union[Class, str]) -> Union[dict, list, Class, int, float, str, bool, datetime.date, datetime.datetime]:
"""Deserializes dict, list, str into an object.
:param data: dict, list or str.
:param klass: class literal, or string of class name.
:return: object.
"""
if data is None:
return None
if klass in (int, float, str, bool):
return _deserialize_primitive(data, klass)
elif klass == object:
return _deserialize_object(data)
elif klass == datetime.date:
return deserialize_date(data)
elif klass == datetime.datetime:
return deserialize_datetime(data)
elif type(klass) == typing.GenericMeta:
if klass.__extra__ == list:
return _deserialize_list(data, klass.__args__[0])
if klass.__extra__ == dict:
return _deserialize_dict(data, klass.__args__[1])
else:
return deserialize_model(data, klass)
def _deserialize_primitive(data, klass: Class) -> Union[Class, int, float, str, bool]:
"""Deserializes to primitive type.
:param data: data to deserialize.
:param klass: class literal.
:return: int, float, str, bool.
"""
try:
value = klass(data)
except (UnicodeEncodeError, TypeError):
value = data
return value
def _deserialize_object(value: T) -> T:
"""Return an original value.
:return: object.
"""
return value
def deserialize_date(string: str) -> datetime.date:
"""Deserializes string to date.
:param string: str.
:return: date.
"""
try:
from dateutil.parser import parse
return parse(string).date()
except ImportError:
return string
def deserialize_datetime(string: str) -> datetime.datetime:
"""Deserializes string to datetime.
The string should be in iso8601 datetime format.
:param string: str.
:return: datetime.
"""
try:
from dateutil.parser import parse
return parse(string)
except ImportError:
return string
def deserialize_model(data: Union[dict, list], klass: T) -> T:
"""Deserializes list or dict to model.
:param data: dict, list.
:param klass: class literal.
:return: model object.
"""
instance = klass()
if not instance.openapi_types:
return data
if data is not None and isinstance(data, (list, dict)):
for attr, attr_type in instance.openapi_types.items():
attr_key = instance.attribute_map[attr]
if attr_key in data:
value = data[attr_key]
setattr(instance, attr, _deserialize(value, attr_type))
return instance
def _deserialize_list(data: list, boxed_type) -> list:
"""Deserializes a list and its elements.
:param data: list to deserialize.
:param boxed_type: class literal.
:return: deserialized list.
"""
return [_deserialize(sub_data, boxed_type) for sub_data in data]
def _deserialize_dict(data: dict, boxed_type) -> dict:
"""Deserializes a dict and its elements.
:param data: dict to deserialize.
:param boxed_type: class literal.
:return: deserialized dict.
"""
return {k: _deserialize(v, boxed_type) for k, v in data.items()}

View File

@ -0,0 +1,46 @@
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>org.openapitools</groupId>
<artifactId>PythonAiohttpServer</artifactId>
<packaging>pom</packaging>
<version>1.0-SNAPSHOT</version>
<name>Python Aiohttp Server</name>
<build>
<plugins>
<plugin>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>copy-dependencies</goal>
</goals>
<configuration>
<outputDirectory>${project.build.directory}</outputDirectory>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>1.2.1</version>
<executions>
<execution>
<id>pytest-test</id>
<phase>integration-test</phase>
<goals>
<goal>exec</goal>
</goals>
<configuration>
<executable>make</executable>
<arguments>
<argument>test-all</argument>
</arguments>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@ -0,0 +1,3 @@
connexion[aiohttp,swagger-ui] == 2.0.2
swagger-ui-bundle == 0.0.2
aiohttp_jinja2 == 1.1.0

View File

@ -0,0 +1,6 @@
coverage>=4.0.3
pytest>=1.3.7
pluggy>=0.3.1
py>=1.4.31
randomize>=0.13
pytest-aiohttp>=0.3.0

View File

@ -0,0 +1,32 @@
#!/bin/bash
REQUIREMENTS_FILE=requirements.txt
TEST_REQUIREMENTS_FILE=test-requirements.txt
REQUIREMENTS_OUT=requirements.txt.log
SETUP_OUT=*.egg-info
VENV=.venv
DEACTIVE=false
export LC_ALL=en_US.UTF-8
export LANG=en_US.UTF-8
### set virtualenv
if [ -z "$VIRTUAL_ENV" ]; then
virtualenv $VENV --no-site-packages --always-copy --python python3
source $VENV/bin/activate
DEACTIVE=true
fi
### install dependencies
pip install -r $REQUIREMENTS_FILE -r $TEST_REQUIREMENTS_FILE | tee -a $REQUIREMENTS_OUT
### run tests
pytest || exit 1
### static analysis of code
flake8 --show-source petstore_api/
### deactivate virtualenv
if [ $DEACTIVE == true ]; then
deactivate
fi

View File

@ -0,0 +1,17 @@
import logging
import pytest
import os
import connexion
@pytest.fixture
def client(loop, aiohttp_client):
logging.getLogger('connexion.operation').setLevel('ERROR')
options = {
"swagger_ui": True
}
specification_dir = os.path.join(os.path.dirname(__file__), '..', 'openapi_server', 'openapi')
app = connexion.AioHttpApp(__name__, specification_dir=specification_dir, options=options)
app.add_api('openapi.yaml', pass_context_arg_name='request')
return loop.run_until_complete(aiohttp_client(app.app))

View File

@ -0,0 +1,200 @@
# coding: utf-8
import pytest
import json
from aiohttp import web
from aiohttp import FormData
from openapi_server.models.api_response import ApiResponse
from openapi_server.models.pet import Pet
@pytest.mark.skip("Connexion does not support multiple consummes. See https://github.com/zalando/connexion/pull/760")
async def test_add_pet(client):
"""Test case for add_pet
Add a new pet to the store
"""
body = {
"photoUrls" : [ "photoUrls", "photoUrls" ],
"name" : "doggie",
"id" : 0,
"category" : {
"name" : "name",
"id" : 6
},
"tags" : [ {
"name" : "name",
"id" : 1
}, {
"name" : "name",
"id" : 1
} ],
"status" : "available"
}
headers = {
'Content-Type': 'application/json',
'Authorization': 'Bearer special-key',
}
response = await client.request(
method='POST',
path='/v2/pet',
headers=headers,
json=body,
)
assert response.status == 200, 'Response body is : ' + (await response.read()).decode('utf-8')
async def test_delete_pet(client):
"""Test case for delete_pet
Deletes a pet
"""
headers = {
'api_key': 'api_key_example',
'Authorization': 'Bearer special-key',
}
response = await client.request(
method='DELETE',
path='/v2/pet/{pet_id}'.format(pet_id=56),
headers=headers,
)
assert response.status == 200, 'Response body is : ' + (await response.read()).decode('utf-8')
async def test_find_pets_by_status(client):
"""Test case for find_pets_by_status
Finds Pets by status
"""
params = [('status', 'available')]
headers = {
'Accept': 'application/json',
'Authorization': 'Bearer special-key',
}
response = await client.request(
method='GET',
path='/v2/pet/findByStatus',
headers=headers,
params=params,
)
assert response.status == 200, 'Response body is : ' + (await response.read()).decode('utf-8')
async def test_find_pets_by_tags(client):
"""Test case for find_pets_by_tags
Finds Pets by tags
"""
params = [('tags', 'tags_example')]
headers = {
'Accept': 'application/json',
'Authorization': 'Bearer special-key',
}
response = await client.request(
method='GET',
path='/v2/pet/findByTags',
headers=headers,
params=params,
)
assert response.status == 200, 'Response body is : ' + (await response.read()).decode('utf-8')
async def test_get_pet_by_id(client):
"""Test case for get_pet_by_id
Find pet by ID
"""
headers = {
'Accept': 'application/json',
'api_key': 'special-key',
}
response = await client.request(
method='GET',
path='/v2/pet/{pet_id}'.format(pet_id=56),
headers=headers,
)
assert response.status == 200, 'Response body is : ' + (await response.read()).decode('utf-8')
@pytest.mark.skip("Connexion does not support multiple consummes. See https://github.com/zalando/connexion/pull/760")
async def test_update_pet(client):
"""Test case for update_pet
Update an existing pet
"""
body = {
"photoUrls" : [ "photoUrls", "photoUrls" ],
"name" : "doggie",
"id" : 0,
"category" : {
"name" : "name",
"id" : 6
},
"tags" : [ {
"name" : "name",
"id" : 1
}, {
"name" : "name",
"id" : 1
} ],
"status" : "available"
}
headers = {
'Content-Type': 'application/json',
'Authorization': 'Bearer special-key',
}
response = await client.request(
method='PUT',
path='/v2/pet',
headers=headers,
json=body,
)
assert response.status == 200, 'Response body is : ' + (await response.read()).decode('utf-8')
@pytest.mark.skip("application/x-www-form-urlencoded not supported by Connexion")
async def test_update_pet_with_form(client):
"""Test case for update_pet_with_form
Updates a pet in the store with form data
"""
headers = {
'Content-Type': 'application/x-www-form-urlencoded',
'Authorization': 'Bearer special-key',
}
data = {
'name': 'name_example',
'status': 'status_example'
}
response = await client.request(
method='POST',
path='/v2/pet/{pet_id}'.format(pet_id=56),
headers=headers,
data=data,
)
assert response.status == 200, 'Response body is : ' + (await response.read()).decode('utf-8')
@pytest.mark.skip("multipart/form-data not supported by Connexion")
async def test_upload_file(client):
"""Test case for upload_file
uploads an image
"""
headers = {
'Accept': 'application/json',
'Content-Type': 'multipart/form-data',
'Authorization': 'Bearer special-key',
}
data = FormData()
data.add_field('additional_metadata', 'additional_metadata_example')
data.add_field('file', (BytesIO(b'some file data'), 'file.txt'))
response = await client.request(
method='POST',
path='/v2/pet/{pet_id}/uploadImage'.format(pet_id=56),
headers=headers,
data=data,
)
assert response.status == 200, 'Response body is : ' + (await response.read()).decode('utf-8')

View File

@ -0,0 +1,76 @@
# coding: utf-8
import pytest
import json
from aiohttp import web
from openapi_server.models.order import Order
async def test_delete_order(client):
"""Test case for delete_order
Delete purchase order by ID
"""
headers = {
}
response = await client.request(
method='DELETE',
path='/v2/store/order/{order_id}'.format(order_id='order_id_example'),
headers=headers,
)
assert response.status == 200, 'Response body is : ' + (await response.read()).decode('utf-8')
async def test_get_inventory(client):
"""Test case for get_inventory
Returns pet inventories by status
"""
headers = {
'Accept': 'application/json',
'api_key': 'special-key',
}
response = await client.request(
method='GET',
path='/v2/store/inventory',
headers=headers,
)
assert response.status == 200, 'Response body is : ' + (await response.read()).decode('utf-8')
async def test_get_order_by_id(client):
"""Test case for get_order_by_id
Find purchase order by ID
"""
headers = {
'Accept': 'application/json',
}
response = await client.request(
method='GET',
path='/v2/store/order/{order_id}'.format(order_id=5),
headers=headers,
)
assert response.status == 200, 'Response body is : ' + (await response.read()).decode('utf-8')
@pytest.mark.skip("*/* not supported by Connexion. Use application/json instead. See https://github.com/zalando/connexion/pull/760")
async def test_place_order(client):
"""Test case for place_order
Place an order for a pet
"""
body = {}
headers = {
'Accept': 'application/json',
'Content-Type': 'application/json',
}
response = await client.request(
method='POST',
path='/v2/store/order',
headers=headers,
json=body,
)
assert response.status == 200, 'Response body is : ' + (await response.read()).decode('utf-8')

View File

@ -0,0 +1,149 @@
# coding: utf-8
import pytest
import json
from aiohttp import web
from openapi_server.models.user import User
@pytest.mark.skip("*/* not supported by Connexion. Use application/json instead. See https://github.com/zalando/connexion/pull/760")
async def test_create_user(client):
"""Test case for create_user
Create user
"""
body = {}
headers = {
'Content-Type': 'application/json',
}
response = await client.request(
method='POST',
path='/v2/user',
headers=headers,
json=body,
)
assert response.status == 200, 'Response body is : ' + (await response.read()).decode('utf-8')
@pytest.mark.skip("*/* not supported by Connexion. Use application/json instead. See https://github.com/zalando/connexion/pull/760")
async def test_create_users_with_array_input(client):
"""Test case for create_users_with_array_input
Creates list of users with given input array
"""
body = []
headers = {
'Content-Type': 'application/json',
}
response = await client.request(
method='POST',
path='/v2/user/createWithArray',
headers=headers,
json=body,
)
assert response.status == 200, 'Response body is : ' + (await response.read()).decode('utf-8')
@pytest.mark.skip("*/* not supported by Connexion. Use application/json instead. See https://github.com/zalando/connexion/pull/760")
async def test_create_users_with_list_input(client):
"""Test case for create_users_with_list_input
Creates list of users with given input array
"""
body = []
headers = {
'Content-Type': 'application/json',
}
response = await client.request(
method='POST',
path='/v2/user/createWithList',
headers=headers,
json=body,
)
assert response.status == 200, 'Response body is : ' + (await response.read()).decode('utf-8')
async def test_delete_user(client):
"""Test case for delete_user
Delete user
"""
headers = {
}
response = await client.request(
method='DELETE',
path='/v2/user/{username}'.format(username='username_example'),
headers=headers,
)
assert response.status == 200, 'Response body is : ' + (await response.read()).decode('utf-8')
async def test_get_user_by_name(client):
"""Test case for get_user_by_name
Get user by user name
"""
headers = {
'Accept': 'application/json',
}
response = await client.request(
method='GET',
path='/v2/user/{username}'.format(username='username_example'),
headers=headers,
)
assert response.status == 200, 'Response body is : ' + (await response.read()).decode('utf-8')
async def test_login_user(client):
"""Test case for login_user
Logs user into the system
"""
params = [('username', 'username_example'),
('password', 'password_example')]
headers = {
'Accept': 'application/json',
}
response = await client.request(
method='GET',
path='/v2/user/login',
headers=headers,
params=params,
)
assert response.status == 200, 'Response body is : ' + (await response.read()).decode('utf-8')
async def test_logout_user(client):
"""Test case for logout_user
Logs out current logged in user session
"""
headers = {
}
response = await client.request(
method='GET',
path='/v2/user/logout',
headers=headers,
)
assert response.status == 200, 'Response body is : ' + (await response.read()).decode('utf-8')
@pytest.mark.skip("*/* not supported by Connexion. Use application/json instead. See https://github.com/zalando/connexion/pull/760")
async def test_update_user(client):
"""Test case for update_user
Updated user
"""
body = {}
headers = {
'Content-Type': 'application/json',
}
response = await client.request(
method='PUT',
path='/v2/user/{username}'.format(username='username_example'),
headers=headers,
json=body,
)
assert response.status == 200, 'Response body is : ' + (await response.read()).decode('utf-8')

View File

@ -0,0 +1,20 @@
#!/bin/bash
REQUIREMENTS_OUT=test-requirements.txt.log
SETUP_OUT=*.egg-info
VENV=.venv
clean:
rm -rf $(REQUIREMENTS_OUT)
rm -rf $(SETUP_OUT)
rm -rf $(VENV)
rm -rf .tox
rm -rf .coverage
find . -name "*.py[oc]" -delete
find . -name "__pycache__" -delete
test: clean
bash ./test_python2.sh
test-all: clean
bash ./test_python2.sh

View File

@ -0,0 +1,48 @@
from typing import List
def info_from_api_key(api_key, required_scopes):
"""
Check and retrieve authentication information from api_key.
Returned value will be passed in 'token_info' parameter of your operation function, if there is one.
'sub' or 'uid' will be set in 'user' parameter of your operation function, if there is one.
:param api_key API key provided by Authorization header
:type api_key: str
:param required_scopes Always None. Used for other authentication method
:type required_scopes: None
:return: Information attached to provided api_key or None if api_key is invalid or does not allow access to called API
:rtype: dict | None
"""
return {'uid': 'user_id'}
def info_from_petstore_auth(token):
"""
Validate and decode token.
Returned value will be passed in 'token_info' parameter of your operation function, if there is one.
'sub' or 'uid' will be set in 'user' parameter of your operation function, if there is one.
'scope' or 'scopes' will be passed to scope validation function.
:param token Token provided by Authorization header
:type token: str
:return: Decoded token information or None if token is invalid
:rtype: dict | None
"""
return {'scopes': ['read:pets', 'write:pets'], 'uid': 'user_id'}
def validate_scope_petstore_auth(required_scopes, token_scopes):
"""
Validate required scopes are included in token scope
:param required_scopes Required scope to access called API
:type required_scopes: List[str]
:param token_scopes Scope present in token
:type token_scopes: List[str]
:return: True if access to called API is allowed
:rtype: bool
"""
return set(required_scopes).issubset(set(token_scopes))

Some files were not shown because too many files have changed in this diff Show More