From 1cb04dc8d5640fa0153febd666fe8aa5a7e8935d Mon Sep 17 00:00:00 2001 From: Hugo Mercado Date: Sun, 28 Apr 2019 15:34:13 -0500 Subject: [PATCH 01/11] added generator class for python flask. --- .../python/PythonFlaskConnexionCodegen.java | 714 ++++++++++++++++++ 1 file changed, 714 insertions(+) create mode 100644 src/main/java/io/swagger/codegen/v3/generators/python/PythonFlaskConnexionCodegen.java diff --git a/src/main/java/io/swagger/codegen/v3/generators/python/PythonFlaskConnexionCodegen.java b/src/main/java/io/swagger/codegen/v3/generators/python/PythonFlaskConnexionCodegen.java new file mode 100644 index 0000000000..b7542e26ab --- /dev/null +++ b/src/main/java/io/swagger/codegen/v3/generators/python/PythonFlaskConnexionCodegen.java @@ -0,0 +1,714 @@ +package io.swagger.codegen.v3.generators.python; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.google.common.collect.ArrayListMultimap; +import com.google.common.collect.Lists; +import com.google.common.collect.Multimap; + +//import io.swagger.codegen.*; +import io.swagger.codegen.v3.*; +import io.swagger.codegen.v3.generators.DefaultCodegenConfig; + +import java.io.File; +import java.util.*; + +import io.swagger.v3.core.util.Yaml; +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.Paths; +import io.swagger.v3.oas.models.media.*; +import io.swagger.v3.oas.models.parameters.Parameter; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import static io.swagger.codegen.v3.generators.handlebars.ExtensionHelper.getBooleanValue; + +public class PythonFlaskConnexionCodegen extends DefaultCodegenConfig { + + private static final Logger LOGGER = LoggerFactory.getLogger(PythonFlaskConnexionCodegen.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 regexModifiers; + + public PythonFlaskConnexionCodegen() { + 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", + "float", "int", "str", "date", "datetime")); + + // 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(); + 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("swagger_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(); + //apiTemplateFiles.clear(); + + if (additionalProperties.containsKey(CodegenConstants.PACKAGE_NAME)) { + setPackageName((String) additionalProperties.get(CodegenConstants.PACKAGE_NAME)); + } else { + setPackageName("swagger_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("swagger.mustache", packageName + File.separatorChar + "swagger", "swagger.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 io.swagger.codegen.CodegenType + */ + @Override + public CodegenType getTag() { + return CodegenType.SERVER; + } + + /** + * Configures a friendly name for the generator. This will be used by the generator + * to select the library with the -l flag. + * + * @return the friendly name for the generator + */ + @Override + public String getName() { + 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."; + } + + @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 schemaProperty) { + if (schemaProperty instanceof ArraySchema) { + ArraySchema ap = (ArraySchema) schemaProperty; + Schema inner = ap.getItems(); + return getSchemaType(schemaProperty) + "[" + getTypeDeclaration(inner) + "]"; + } else if (schemaProperty instanceof MapSchema) { + MapSchema mp = (MapSchema) schemaProperty; + Object inner = mp.getAdditionalProperties(); + + if (inner instanceof Schema) { + return getSchemaType(schemaProperty) + "[str, " + getTypeDeclaration((Schema) inner) + "]"; + } + } + return super.getTypeDeclaration(schemaProperty); + } + + @Override + public String getSchemaType(Schema p) { + String swaggerType = super.getSchemaType(p); + String type = null; + if (typeMapping.containsKey(swaggerType)) { + type = typeMapping.get(swaggerType); + if (languageSpecificPrimitives.contains(type)) { + return type; + } + } else { + type = toModelName(swaggerType); + } + return type; + } + + @Override + public void preprocessOpenAPI(OpenAPI openAPI) { + // need vendor extensions for x-swagger-router-controller + Paths paths = openAPI.getPaths(); + if(paths != null) { + for(String pathname : paths.keySet()) { + PathItem path = paths.get(pathname); + Map operationMap = path.readOperationsMap(); + if(operationMap != null) { + for(PathItem.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().get("x-swagger-router-controller") == null) { + operation.getExtensions().put( + "x-swagger-router-controller", + controllerPackage + "." + toApiFilename(tag) + ); + } + for (Parameter param: operation.getParameters()) { + // sanitize the param name but don't underscore it since it's used for request mapping + String name = param.getName(); + String paramName = sanitizeName(name); + if (!paramName.equals(name)) { + LOGGER.warn(name + " cannot be used as parameter name with flask-connexion and was sanitized as " + paramName); + } + param.setName(paramName); + } + } + } + } + } + } + + @SuppressWarnings("unchecked") + private static List> getOperations(Map objs) { + List> result = new ArrayList>(); + Map apiInfo = (Map) objs.get("apiInfo"); + List> apis = (List>) apiInfo.get("apis"); + for (Map api : apis) { + result.add((Map) api.get("operations")); + } + return result; + } + + private static List> sortOperationsByPath(List ops) { + Multimap opsByPath = ArrayListMultimap.create(); + + for (CodegenOperation op : ops) { + opsByPath.put(op.path, op); + } + + List> opsByPathList = new ArrayList>(); + for (Map.Entry> entry : opsByPath.asMap().entrySet()) { + Map opsByPathEntry = new HashMap(); + opsByPathList.add(opsByPathEntry); + opsByPathEntry.put("path", entry.getKey()); + opsByPathEntry.put("operation", entry.getValue()); + List operationsForThisPath = Lists.newArrayList(entry.getValue()); + CodegenOperation codegenOperation = operationsForThisPath.get(operationsForThisPath.size() - 1); + codegenOperation.getVendorExtensions().put(CodegenConstants.HAS_MORE_EXT_NAME, Boolean.FALSE); + + if (opsByPathList.size() < opsByPath.asMap().size()) { + opsByPathEntry.put("hasMore", "true"); + } + } + + return opsByPathList; + } + + @Override + public Map postProcessSupportingFileData(Map objs) { + OpenAPI swagger = (OpenAPI) objs.get("openapi"); + if(swagger != null) { + try { + objs.put("opennapi-yaml", Yaml.mapper().writeValueAsString(swagger)); + } catch (JsonProcessingException e) { + LOGGER.error(e.getMessage(), e); + } + } + for (Map operations : getOperations(objs)) { + @SuppressWarnings("unchecked") + List ops = (List) operations.get("operation"); + + List> 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(); + } + + // 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) { + // don't do name =removeNonNameElementToCamelCase(name); // this breaks connexion, which does not modify param names before sending them + if (reservedWords.contains(name)) { + return escapeReservedWord(name); + } + // Param name is already sanitized in swagger spec processing + return 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 getDefaultTemplateDir() { + return "changeme"; + } + + @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 schemaProperty Swagger property object + * @return string presentation of the default value of the property + */ + @Override + public String toDefaultValue(Schema schemaProperty) { + if (schemaProperty instanceof StringSchema) { + StringSchema dp = (StringSchema) schemaProperty; + if (dp.getDefault() != null) { + return "'" + dp.getDefault() + "'"; + } + } else if (schemaProperty instanceof BooleanSchema) { + BooleanSchema dp = (BooleanSchema) schemaProperty; + if (dp.getDefault() != null) { + if (dp.getDefault().toString().equalsIgnoreCase("false")) + return "False"; + else + return "True"; + } + } else if (schemaProperty instanceof DateSchema) { + // TODO + } else if (schemaProperty instanceof DateTimeSchema) { + // TODO + } else if (schemaProperty instanceof NumberSchema) { + NumberSchema dp = (NumberSchema) schemaProperty; + if (dp.getDefault() != null) { + return dp.getDefault().toString(); + } + } else if (schemaProperty instanceof IntegerSchema) { + IntegerSchema dp = (IntegerSchema) schemaProperty; + if (dp.getDefault() != null) { + return dp.getDefault().toString(); + } + } + + return null; + } + + @Override + public void setParameterExampleValue(CodegenParameter p) { + String example; + + if (p.defaultValue == null) { + example = p.example; + } else { + example = p.defaultValue; + } + + 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 (getBooleanValue(p, CodegenConstants.IS_LIST_CONTAINER_EXT_NAME)) { + if (getBooleanValue(p, CodegenConstants.IS_BODY_PARAM_EXT_NAME)) { + example = "[" + example + "]"; + } + } else if (getBooleanValue(p, CodegenConstants.IS_MAP_CONTAINER_EXT_NAME)) { + 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 postProcessModels(Map objs) { + // process enum in models + return postProcessModelsEnum(objs); + } + + @Override + public void postProcessParameter(CodegenParameter parameter){ + postProcessPattern(parameter.pattern, parameter.vendorExtensions); + } + + /* + * The swagger 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 vendorExtensions){ + if(pattern != null) { + int i = pattern.lastIndexOf('/'); + + //Must follow Perl /pattern/modifiers convention + if(pattern.charAt(0) != '/' || i < 2) { + pattern = String.format("/%s/", pattern);; + i = pattern.lastIndexOf('/'); + } + + String regex = pattern.substring(1, i).replace("'", "\\'"); + List modifiers = new ArrayList(); + + 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); + } + } +} From 0bd0467abee3b0be2a1ce98fac975d6cbbac99d2 Mon Sep 17 00:00:00 2001 From: Hugo Mercado Date: Sun, 28 Apr 2019 15:35:08 -0500 Subject: [PATCH 02/11] added mustaches templates for python flask connexion --- .../pythonFlaskConnexion/Dockerfile.mustache | 31 ++++ .../pythonFlaskConnexion/README.mustache | 60 +++++++ .../pythonFlaskConnexion/__init__.mustache | 0 .../__init__model.mustache | 7 + .../__init__test.mustache | 16 ++ .../pythonFlaskConnexion/__main__.mustache | 21 +++ .../pythonFlaskConnexion/base_model_.mustache | 73 ++++++++ .../pythonFlaskConnexion/controller.mustache | 112 ++++++++++++ .../controller_test.mustache | 51 ++++++ .../dockerignore.mustache | 72 ++++++++ .../pythonFlaskConnexion/encoder.mustache | 20 +++ .../pythonFlaskConnexion/git_push.sh.mustache | 52 ++++++ .../pythonFlaskConnexion/gitignore.mustache | 64 +++++++ .../pythonFlaskConnexion/model.mustache | 161 ++++++++++++++++++ .../pythonFlaskConnexion/param_type.mustache | 1 + .../requirements.mustache | 6 + .../pythonFlaskConnexion/setup.mustache | 35 ++++ .../pythonFlaskConnexion/swagger.mustache | 1 + .../test-requirements.mustache | 6 + .../pythonFlaskConnexion/tox.mustache | 10 ++ .../pythonFlaskConnexion/travis.mustache | 16 ++ .../pythonFlaskConnexion/util.mustache | 141 +++++++++++++++ 22 files changed, 956 insertions(+) create mode 100644 src/main/resources/mustache/pythonFlaskConnexion/Dockerfile.mustache create mode 100644 src/main/resources/mustache/pythonFlaskConnexion/README.mustache create mode 100644 src/main/resources/mustache/pythonFlaskConnexion/__init__.mustache create mode 100644 src/main/resources/mustache/pythonFlaskConnexion/__init__model.mustache create mode 100644 src/main/resources/mustache/pythonFlaskConnexion/__init__test.mustache create mode 100644 src/main/resources/mustache/pythonFlaskConnexion/__main__.mustache create mode 100644 src/main/resources/mustache/pythonFlaskConnexion/base_model_.mustache create mode 100644 src/main/resources/mustache/pythonFlaskConnexion/controller.mustache create mode 100644 src/main/resources/mustache/pythonFlaskConnexion/controller_test.mustache create mode 100644 src/main/resources/mustache/pythonFlaskConnexion/dockerignore.mustache create mode 100644 src/main/resources/mustache/pythonFlaskConnexion/encoder.mustache create mode 100755 src/main/resources/mustache/pythonFlaskConnexion/git_push.sh.mustache create mode 100644 src/main/resources/mustache/pythonFlaskConnexion/gitignore.mustache create mode 100644 src/main/resources/mustache/pythonFlaskConnexion/model.mustache create mode 100644 src/main/resources/mustache/pythonFlaskConnexion/param_type.mustache create mode 100644 src/main/resources/mustache/pythonFlaskConnexion/requirements.mustache create mode 100644 src/main/resources/mustache/pythonFlaskConnexion/setup.mustache create mode 100644 src/main/resources/mustache/pythonFlaskConnexion/swagger.mustache create mode 100644 src/main/resources/mustache/pythonFlaskConnexion/test-requirements.mustache create mode 100644 src/main/resources/mustache/pythonFlaskConnexion/tox.mustache create mode 100644 src/main/resources/mustache/pythonFlaskConnexion/travis.mustache create mode 100644 src/main/resources/mustache/pythonFlaskConnexion/util.mustache diff --git a/src/main/resources/mustache/pythonFlaskConnexion/Dockerfile.mustache b/src/main/resources/mustache/pythonFlaskConnexion/Dockerfile.mustache new file mode 100644 index 0000000000..f040d41ad6 --- /dev/null +++ b/src/main/resources/mustache/pythonFlaskConnexion/Dockerfile.mustache @@ -0,0 +1,31 @@ +{{#supportPython2}} +FROM python:2-alpine +{{/supportPython2}} +{{^supportPython2}} +FROM python:3-alpine +{{/supportPython2}} + +RUN mkdir -p /usr/src/app +WORKDIR /usr/src/app + +COPY requirements.txt /usr/src/app/ + +{{#supportPython2}} +RUN pip install --no-cache-dir -r requirements.txt +{{/supportPython2}} +{{^supportPython2}} +RUN pip3 install --no-cache-dir -r requirements.txt +{{/supportPython2}} + +COPY . /usr/src/app + +EXPOSE {{serverPort}} + +{{#supportPython2}} +ENTRYPOINT ["python"] +{{/supportPython2}} +{{^supportPython2}} +ENTRYPOINT ["python3"] +{{/supportPython2}} + +CMD ["-m", "{{packageName}}"] \ No newline at end of file diff --git a/src/main/resources/mustache/pythonFlaskConnexion/README.mustache b/src/main/resources/mustache/pythonFlaskConnexion/README.mustache new file mode 100644 index 0000000000..b73b9e311b --- /dev/null +++ b/src/main/resources/mustache/pythonFlaskConnexion/README.mustache @@ -0,0 +1,60 @@ +# Swagger generated server + +## Overview +This server was generated by the [swagger-codegen](https://github.com/swagger-api/swagger-codegen) project. By using the +[OpenAPI-Spec](https://github.com/swagger-api/swagger-core/wiki) from a remote server, you can easily generate a server stub. This +is an example of building a swagger-enabled Flask server. + +This example uses the [Connexion](https://github.com/zalando/connexion) library on top of Flask. + +## Requirements +{{#supportPython2}} +Python 2.7+ +{{/supportPython2}} +{{^supportPython2}} +Python 3.5.2+ +{{/supportPython2}} + +## Usage +To run the server, please execute the following from the root directory: + +``` +{{#supportPython2}} +pip install -r requirements.txt +python -m {{packageName}} +{{/supportPython2}} +{{^supportPython2}} +pip3 install -r requirements.txt +python3 -m {{packageName}} +{{/supportPython2}} +``` + +and open your browser to here: + +``` +http://localhost:{{serverPort}}{{contextPath}}/ui/ +``` + +Your Swagger definition lives here: + +``` +http://localhost:{{serverPort}}{{contextPath}}/swagger.json +``` + +To launch the integration tests, use tox: +``` +sudo pip install tox +tox +``` + +## Running with Docker + +To run the server on a Docker container, please execute the following from the root directory: + +```bash +# building the image +docker build -t {{packageName}} . + +# starting up a container +docker run -p {{serverPort}}:{{serverPort}} {{packageName}} +``` \ No newline at end of file diff --git a/src/main/resources/mustache/pythonFlaskConnexion/__init__.mustache b/src/main/resources/mustache/pythonFlaskConnexion/__init__.mustache new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/main/resources/mustache/pythonFlaskConnexion/__init__model.mustache b/src/main/resources/mustache/pythonFlaskConnexion/__init__model.mustache new file mode 100644 index 0000000000..98b56e3d15 --- /dev/null +++ b/src/main/resources/mustache/pythonFlaskConnexion/__init__model.mustache @@ -0,0 +1,7 @@ +# coding: utf-8 + +# flake8: noqa +from __future__ import absolute_import +# import models into model package +{{#models}}{{#model}}from {{modelPackage}}.{{classFilename}} import {{classname}}{{/model}} +{{/models}} \ No newline at end of file diff --git a/src/main/resources/mustache/pythonFlaskConnexion/__init__test.mustache b/src/main/resources/mustache/pythonFlaskConnexion/__init__test.mustache new file mode 100644 index 0000000000..9cea80bc63 --- /dev/null +++ b/src/main/resources/mustache/pythonFlaskConnexion/__init__test.mustache @@ -0,0 +1,16 @@ +import logging + +import connexion +from flask_testing import TestCase + +from {{packageName}}.encoder import JSONEncoder + + +class BaseTestCase(TestCase): + + def create_app(self): + logging.getLogger('connexion.operation').setLevel('ERROR') + app = connexion.App(__name__, specification_dir='../swagger/') + app.app.json_encoder = JSONEncoder + app.add_api('swagger.yaml') + return app.app diff --git a/src/main/resources/mustache/pythonFlaskConnexion/__main__.mustache b/src/main/resources/mustache/pythonFlaskConnexion/__main__.mustache new file mode 100644 index 0000000000..1075634722 --- /dev/null +++ b/src/main/resources/mustache/pythonFlaskConnexion/__main__.mustache @@ -0,0 +1,21 @@ +{{#supportPython2}} +#!/usr/bin/env python +{{/supportPython2}} +{{^supportPython2}} +#!/usr/bin/env python3 +{{/supportPython2}} + +import connexion + +from {{packageName}} import encoder + + +def main(): + app = connexion.App(__name__, specification_dir='./swagger/') + app.app.json_encoder = encoder.JSONEncoder + app.add_api('swagger.yaml', arguments={'title': '{{appName}}'}) + app.run(port={{serverPort}}) + + +if __name__ == '__main__': + main() diff --git a/src/main/resources/mustache/pythonFlaskConnexion/base_model_.mustache b/src/main/resources/mustache/pythonFlaskConnexion/base_model_.mustache new file mode 100644 index 0000000000..54517a06d5 --- /dev/null +++ b/src/main/resources/mustache/pythonFlaskConnexion/base_model_.mustache @@ -0,0 +1,73 @@ +import pprint + +import six +{{^supportPython2}} +import typing +{{/supportPython2}} + +from {{packageName}} import util +{{^supportPython2}} + +T = typing.TypeVar('T') +{{/supportPython2}} + + +class Model(object): + # swaggerTypes: The key is attribute name and the + # value is attribute type. + swagger_types = {} + + # attributeMap: The key is attribute name and the + # value is json key in definition. + attribute_map = {} + + @classmethod + def from_dict(cls{{^supportPython2}}: typing.Type[T]{{/supportPython2}}, dikt){{^supportPython2}} -> T{{/supportPython2}}: + """Returns the dict as a model""" + return util.deserialize_model(dikt, cls) + + def to_dict(self): + """Returns the model properties as a dict + + :rtype: dict + """ + result = {} + + for attr, _ in six.iteritems(self.swagger_types): + value = getattr(self, attr) + if isinstance(value, list): + result[attr] = list(map( + lambda x: x.to_dict() if hasattr(x, "to_dict") else x, + value + )) + elif hasattr(value, "to_dict"): + result[attr] = value.to_dict() + elif isinstance(value, dict): + result[attr] = dict(map( + lambda item: (item[0], item[1].to_dict()) + if hasattr(item[1], "to_dict") else item, + value.items() + )) + else: + result[attr] = value + + return result + + def to_str(self): + """Returns the string representation of the model + + :rtype: str + """ + 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 diff --git a/src/main/resources/mustache/pythonFlaskConnexion/controller.mustache b/src/main/resources/mustache/pythonFlaskConnexion/controller.mustache new file mode 100644 index 0000000000..243da33e57 --- /dev/null +++ b/src/main/resources/mustache/pythonFlaskConnexion/controller.mustache @@ -0,0 +1,112 @@ +import connexion +import six + +{{#imports}}{{import}} # noqa: E501 +{{/imports}} +from {{packageName}} import util +{{#operations}} +{{#operation}} + + +def {{operationId}}({{#allParams}}{{paramName}}{{^required}}=None{{/required}}{{#hasMore}}, {{/hasMore}}{{/allParams}}): # noqa: E501 + """{{#summary}}{{.}}{{/summary}}{{^summary}}{{operationId}}{{/summary}} + + {{#notes}}{{.}}{{/notes}} # noqa: E501 + + {{#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}} + + :rtype: {{#returnType}}{{.}}{{/returnType}}{{^returnType}}None{{/returnType}} + """ + {{#allParams}} + {{^isContainer}} + {{#isDate}} + {{paramName}} = util.deserialize_date({{paramName}}) + {{/isDate}} + {{#isDateTime}} + {{paramName}} = util.deserialize_datetime({{paramName}}) + {{/isDateTime}} + {{^isPrimitiveType}} + {{^isFile}} + {{^isUuid}} + if connexion.request.is_json: + {{paramName}} = {{baseType}}.from_dict(connexion.request.get_json()) # noqa: E501 + {{/isUuid}} + {{/isFile}} + {{/isPrimitiveType}} + {{/isContainer}} + {{#isListContainer}} + {{#items}} + {{#isDate}} + if connexion.request.is_json: + {{paramName}} = [util.deserialize_date(s) for s in connexion.request.get_json()] # noqa: E501 + {{/isDate}} + {{#isDateTime}} + if connexion.request.is_json: + {{paramName}} = [util.deserialize_datetime(s) for s in connexion.request.get_json()] # noqa: E501 + {{/isDateTime}} + {{#complexType}} + if connexion.request.is_json: + {{paramName}} = [{{complexType}}.from_dict(d) for d in connexion.request.get_json()] # noqa: E501 + {{/complexType}} + {{/items}} + {{/isListContainer}} + {{#isMapContainer}} + {{#items}} + {{#isDate}} + if connexion.request.is_json: + {{paramName}} = {k: util.deserialize_date(v) for k, v in six.iteritems(connexion.request.get_json())} # noqa: E501 + {{/isDate}} + {{#isDateTime}} + if connexion.request.is_json: + {{paramName}} = {k: util.deserialize_datetime(v) for k, v in six.iteritems(connexion.request.get_json())} # noqa: E501 + {{/isDateTime}} + {{#complexType}} + if connexion.request.is_json: + {{paramName}} = {k: {{baseType}}.from_dict(v) for k, v in six.iteritems(connexion.request.get_json())} # noqa: E501 + {{/complexType}} + {{/items}} + {{/isMapContainer}} + {{/allParams}} + return 'do some magic!' +{{/operation}} +{{/operations}} diff --git a/src/main/resources/mustache/pythonFlaskConnexion/controller_test.mustache b/src/main/resources/mustache/pythonFlaskConnexion/controller_test.mustache new file mode 100644 index 0000000000..a41b12f2c2 --- /dev/null +++ b/src/main/resources/mustache/pythonFlaskConnexion/controller_test.mustache @@ -0,0 +1,51 @@ +# coding: utf-8 + +from __future__ import absolute_import + +from flask import json +from six import BytesIO + +{{#imports}}{{import}} # noqa: E501 +{{/imports}} +from {{packageName}}.test import BaseTestCase + + +class {{#operations}}Test{{classname}}(BaseTestCase): + """{{classname}} integration test stubs""" + + {{#operation}} + def test_{{operationId}}(self): + """Test case for {{{operationId}}} + + {{{summary}}} + """ + {{#bodyParam}} + {{paramName}} = {{{example}}} + {{/bodyParam}} + {{#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}} + {{#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}}, + data=json.dumps({{paramName}}){{^consumes}}, + content_type='application/json'{{/consumes}}{{/bodyParam}}{{#headerParams}}{{#-first}}, + headers=headers{{/-first}}{{/headerParams}}{{#formParams}}{{#-first}}, + data=data{{/-first}}{{/formParams}}{{#consumes}}{{#-first}}, + content_type='{{{mediaType}}}'{{/-first}}{{/consumes}}{{#queryParams}}{{#-first}}, + query_string=query_string{{/-first}}{{/queryParams}}) + self.assert200(response, + 'Response body is : ' + response.data.decode('utf-8')) + + {{/operation}} +{{/operations}} + +if __name__ == '__main__': + import unittest + unittest.main() diff --git a/src/main/resources/mustache/pythonFlaskConnexion/dockerignore.mustache b/src/main/resources/mustache/pythonFlaskConnexion/dockerignore.mustache new file mode 100644 index 0000000000..cdd823e64e --- /dev/null +++ b/src/main/resources/mustache/pythonFlaskConnexion/dockerignore.mustache @@ -0,0 +1,72 @@ +.travis.yaml +.swagger-codegen-ignore +README.md +tox.ini +git_push.sh +test-requirements.txt +setup.py + +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +env/ +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +*.egg-info/ +.installed.cfg +*.egg + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*,cover +.hypothesis/ +venv/ +.python-version + +# Translations +*.mo +*.pot + +# Django stuff: +*.log + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +#Ipython Notebook +.ipynb_checkpoints diff --git a/src/main/resources/mustache/pythonFlaskConnexion/encoder.mustache b/src/main/resources/mustache/pythonFlaskConnexion/encoder.mustache new file mode 100644 index 0000000000..e303a0e41a --- /dev/null +++ b/src/main/resources/mustache/pythonFlaskConnexion/encoder.mustache @@ -0,0 +1,20 @@ +from connexion.apps.flask_app import FlaskJSONEncoder +import six + +from {{modelPackage}}.base_model_ import Model + + +class JSONEncoder(FlaskJSONEncoder): + include_nulls = False + + def default(self, o): + if isinstance(o, Model): + dikt = {} + for attr, _ in six.iteritems(o.swagger_types): + value = getattr(o, attr) + if value is None and not self.include_nulls: + continue + attr = o.attribute_map[attr] + dikt[attr] = value + return dikt + return FlaskJSONEncoder.default(self, o) diff --git a/src/main/resources/mustache/pythonFlaskConnexion/git_push.sh.mustache b/src/main/resources/mustache/pythonFlaskConnexion/git_push.sh.mustache new file mode 100755 index 0000000000..f65b794638 --- /dev/null +++ b/src/main/resources/mustache/pythonFlaskConnexion/git_push.sh.mustache @@ -0,0 +1,52 @@ +#!/bin/sh +# ref: https://help.github.com/articles/adding-an-existing-project-to-github-using-the-command-line/ +# +# Usage example: /bin/sh ./git_push.sh wing328 swagger-petstore-perl "minor update" + +git_user_id=$1 +git_repo_id=$2 +release_note=$3 + +if [ "$git_user_id" = "" ]; then + git_user_id="{{{gitUserId}}}" + echo "[INFO] No command line input provided. Set \$git_user_id to $git_user_id" +fi + +if [ "$git_repo_id" = "" ]; then + git_repo_id="{{{gitRepoId}}}" + echo "[INFO] No command line input provided. Set \$git_repo_id to $git_repo_id" +fi + +if [ "$release_note" = "" ]; then + release_note="{{{releaseNote}}}" + echo "[INFO] No command line input provided. Set \$release_note to $release_note" +fi + +# Initialize the local directory as a Git repository +git init + +# Adds the files in the local repository and stages them for commit. +git add . + +# Commits the tracked changes and prepares them to be pushed to a remote repository. +git commit -m "$release_note" + +# Sets the new remote +git_remote=`git remote` +if [ "$git_remote" = "" ]; then # git remote not defined + + if [ "$GIT_TOKEN" = "" ]; then + echo "[INFO] \$GIT_TOKEN (environment variable) is not set. Using the git credential in your environment." + git remote add origin https://github.com/${git_user_id}/${git_repo_id}.git + else + git remote add origin https://${git_user_id}:${GIT_TOKEN}@github.com/${git_user_id}/${git_repo_id}.git + fi + +fi + +git pull origin master + +# Pushes (Forces) the changes in the local repository up to the remote repository +echo "Git pushing to https://github.com/${git_user_id}/${git_repo_id}.git" +git push origin master 2>&1 | grep -v 'To https' + diff --git a/src/main/resources/mustache/pythonFlaskConnexion/gitignore.mustache b/src/main/resources/mustache/pythonFlaskConnexion/gitignore.mustache new file mode 100644 index 0000000000..a655050c26 --- /dev/null +++ b/src/main/resources/mustache/pythonFlaskConnexion/gitignore.mustache @@ -0,0 +1,64 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +env/ +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +*.egg-info/ +.installed.cfg +*.egg + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*,cover +.hypothesis/ +venv/ +.python-version + +# Translations +*.mo +*.pot + +# Django stuff: +*.log + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +#Ipython Notebook +.ipynb_checkpoints diff --git a/src/main/resources/mustache/pythonFlaskConnexion/model.mustache b/src/main/resources/mustache/pythonFlaskConnexion/model.mustache new file mode 100644 index 0000000000..7bb398f9f7 --- /dev/null +++ b/src/main/resources/mustache/pythonFlaskConnexion/model.mustache @@ -0,0 +1,161 @@ +# coding: utf-8 + +from __future__ import absolute_import +from datetime import date, datetime # noqa: F401 + +from typing import List, Dict # noqa: F401 + +from {{modelPackage}}.base_model_ import Model +{{#imports}}{{import}} # noqa: F401,E501 +{{/imports}} +from {{packageName}} import util + + +{{#models}} +{{#model}} +class {{classname}}(Model): + """NOTE: This class is auto generated by the swagger code generator program. + + Do not edit the class manually. + """{{#allowableValues}} + + """ + allowed enum values + """ +{{#enumVars}} + {{name}} = {{{value}}}{{^-last}} +{{/-last}} +{{/enumVars}}{{/allowableValues}} + + def __init__(self{{#vars}}, {{name}}{{^supportPython2}}: {{datatype}}{{/supportPython2}}={{#defaultValue}}{{{defaultValue}}}{{/defaultValue}}{{^defaultValue}}None{{/defaultValue}}{{/vars}}): # noqa: E501 + """{{classname}} - a model defined in Swagger + + {{#vars}} + :param {{name}}: The {{name}} of this {{classname}}. # noqa: E501 + :type {{name}}: {{datatype}} + {{/vars}} + """ + self.swagger_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){{^supportPython2}} -> '{{classname}}'{{/supportPython2}}: + """Returns the dict as a model + + :param dikt: A dict. + :type: dict + :return: The {{name}} of this {{classname}}. # noqa: E501 + :rtype: {{classname}} + """ + return util.deserialize_model(dikt, cls){{#vars}}{{#-first}} + +{{/-first}} + @property + def {{name}}(self){{^supportPython2}} -> {{datatype}}{{/supportPython2}}: + """Gets the {{name}} of this {{classname}}. + + {{#description}} + {{{description}}} # noqa: E501 + {{/description}} + + :return: The {{name}} of this {{classname}}. + :rtype: {{datatype}} + """ + return self._{{name}} + + @{{name}}.setter + def {{name}}(self, {{name}}{{^supportPython2}}: {{datatype}}{{/supportPython2}}): + """Sets the {{name}} of this {{classname}}. + + {{#description}} + {{{description}}} # noqa: E501 + {{/description}} + + :param {{name}}: The {{name}} of this {{classname}}. + :type {{name}}: {{datatype}} + """ +{{#isEnum}} + allowed_values = [{{#allowableValues}}{{#values}}"{{{this}}}"{{^-last}}, {{/-last}}{{/values}}{{/allowableValues}}] # noqa: E501 +{{#isContainer}} +{{#isListContainer}} + if not set({{{name}}}).issubset(set(allowed_values)): + raise ValueError( + "Invalid values for `{{{name}}}` [{0}], must be a subset of [{1}]" # noqa: E501 + .format(", ".join(map(str, set({{{name}}}) - set(allowed_values))), # noqa: E501 + ", ".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}]" # noqa: E501 + .format(", ".join(map(str, set({{{name}}}.keys()) - set(allowed_values))), # noqa: E501 + ", ".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`") # noqa: E501 +{{/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}}`") # noqa: E501 +{{/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}}`") # noqa: E501 +{{/minLength}} +{{#maximum}} + if {{name}} is not None and {{name}} >{{#exclusiveMaximum}}={{/exclusiveMaximum}} {{maximum}}: # noqa: E501 + raise ValueError("Invalid value for `{{name}}`, must be a value less than {{^exclusiveMaximum}}or equal to {{/exclusiveMaximum}}`{{maximum}}`") # noqa: E501 +{{/maximum}} +{{#minimum}} + if {{name}} is not None and {{name}} <{{#exclusiveMinimum}}={{/exclusiveMinimum}} {{minimum}}: # noqa: E501 + raise ValueError("Invalid value for `{{name}}`, must be a value greater than {{^exclusiveMinimum}}or equal to {{/exclusiveMinimum}}`{{minimum}}`") # noqa: E501 +{{/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}}): # noqa: E501 + raise ValueError("Invalid value for `{{name}}`, must be a follow pattern or equal to `{{{pattern}}}`") # noqa: E501 +{{/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}}`") # noqa: E501 +{{/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}}`") # noqa: E501 +{{/minItems}} +{{/hasValidation}} +{{/isEnum}} + + self._{{name}} = {{name}}{{^-last}} + +{{/-last}} +{{/vars}} + +{{/model}} +{{/models}} \ No newline at end of file diff --git a/src/main/resources/mustache/pythonFlaskConnexion/param_type.mustache b/src/main/resources/mustache/pythonFlaskConnexion/param_type.mustache new file mode 100644 index 0000000000..21e7e07ec5 --- /dev/null +++ b/src/main/resources/mustache/pythonFlaskConnexion/param_type.mustache @@ -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}} \ No newline at end of file diff --git a/src/main/resources/mustache/pythonFlaskConnexion/requirements.mustache b/src/main/resources/mustache/pythonFlaskConnexion/requirements.mustache new file mode 100644 index 0000000000..04042d4b1d --- /dev/null +++ b/src/main/resources/mustache/pythonFlaskConnexion/requirements.mustache @@ -0,0 +1,6 @@ +connexion == 1.1.15 +python_dateutil == 2.6.0 +{{#supportPython2}} +typing == 3.5.2.2 +{{/supportPython2}} +setuptools >= 21.0.0 diff --git a/src/main/resources/mustache/pythonFlaskConnexion/setup.mustache b/src/main/resources/mustache/pythonFlaskConnexion/setup.mustache new file mode 100644 index 0000000000..56f8bc1ec1 --- /dev/null +++ b/src/main/resources/mustache/pythonFlaskConnexion/setup.mustache @@ -0,0 +1,35 @@ +# coding: utf-8 + +import sys +from setuptools import setup, find_packages + +NAME = "{{packageName}}" +VERSION = "{{packageVersion}}" +{{#apiInfo}}{{#apis}}{{^hasMore}} +# To install the library, run the following +# +# python setup.py install +# +# prerequisite: setuptools +# http://pypi.python.org/pypi/setuptools + +REQUIRES = ["connexion"] + +setup( + name=NAME, + version=VERSION, + description="{{appName}}", + author_email="{{infoEmail}}", + url="{{packageUrl}}", + keywords=["Swagger", "{{appName}}"], + install_requires=REQUIRES, + packages=find_packages(), + package_data={'': ['swagger/swagger.yaml']}, + include_package_data=True, + entry_points={ + 'console_scripts': ['{{packageName}}={{packageName}}.__main__:main']}, + long_description="""\ + {{appDescription}} + """ +) +{{/hasMore}}{{/apis}}{{/apiInfo}} diff --git a/src/main/resources/mustache/pythonFlaskConnexion/swagger.mustache b/src/main/resources/mustache/pythonFlaskConnexion/swagger.mustache new file mode 100644 index 0000000000..51560926bb --- /dev/null +++ b/src/main/resources/mustache/pythonFlaskConnexion/swagger.mustache @@ -0,0 +1 @@ +{{{swagger-yaml}}} \ No newline at end of file diff --git a/src/main/resources/mustache/pythonFlaskConnexion/test-requirements.mustache b/src/main/resources/mustache/pythonFlaskConnexion/test-requirements.mustache new file mode 100644 index 0000000000..7f8d96e6b4 --- /dev/null +++ b/src/main/resources/mustache/pythonFlaskConnexion/test-requirements.mustache @@ -0,0 +1,6 @@ +flask_testing==0.6.1 +coverage>=4.0.3 +nose>=1.3.7 +pluggy>=0.3.1 +py>=1.4.31 +randomize>=0.13 diff --git a/src/main/resources/mustache/pythonFlaskConnexion/tox.mustache b/src/main/resources/mustache/pythonFlaskConnexion/tox.mustache new file mode 100644 index 0000000000..3efa994317 --- /dev/null +++ b/src/main/resources/mustache/pythonFlaskConnexion/tox.mustache @@ -0,0 +1,10 @@ +[tox] +envlist = {{#supportPython2}}py27, {{/supportPython2}}py35 + +[testenv] +deps=-r{toxinidir}/requirements.txt + -r{toxinidir}/test-requirements.txt + +commands= + nosetests \ + [] \ No newline at end of file diff --git a/src/main/resources/mustache/pythonFlaskConnexion/travis.mustache b/src/main/resources/mustache/pythonFlaskConnexion/travis.mustache new file mode 100644 index 0000000000..167826e42b --- /dev/null +++ b/src/main/resources/mustache/pythonFlaskConnexion/travis.mustache @@ -0,0 +1,16 @@ +# ref: https://docs.travis-ci.com/user/languages/python +language: python +python: +{{#supportPython2}} + - "2.7" +{{/supportPython2}} + - "3.2" + - "3.3" + - "3.4" + - "3.5" + #- "3.5-dev" # 3.5 development branch + #- "nightly" # points to the latest development branch e.g. 3.6-dev +# command to install dependencies +install: "pip install -r requirements.txt" +# command to run tests +script: nosetests diff --git a/src/main/resources/mustache/pythonFlaskConnexion/util.mustache b/src/main/resources/mustache/pythonFlaskConnexion/util.mustache new file mode 100644 index 0000000000..527d1424c3 --- /dev/null +++ b/src/main/resources/mustache/pythonFlaskConnexion/util.mustache @@ -0,0 +1,141 @@ +import datetime + +import six +import typing + + +def _deserialize(data, klass): + """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 six.integer_types or klass in (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): + """Deserializes to primitive type. + + :param data: data to deserialize. + :param klass: class literal. + + :return: int, long, float, str, bool. + :rtype: int | long | float | str | bool + """ + try: + value = klass(data) + except UnicodeEncodeError: + value = six.u(data) + except TypeError: + value = data + return value + + +def _deserialize_object(value): + """Return a original value. + + :return: object. + """ + return value + + +def deserialize_date(string): + """Deserializes string to date. + + :param string: str. + :type string: str + :return: date. + :rtype: date + """ + try: + from dateutil.parser import parse + return parse(string).date() + except ImportError: + return string + + +def deserialize_datetime(string): + """Deserializes string to datetime. + + The string should be in iso8601 datetime format. + + :param string: str. + :type string: str + :return: datetime. + :rtype: datetime + """ + try: + from dateutil.parser import parse + return parse(string) + except ImportError: + return string + + +def deserialize_model(data, klass): + """Deserializes list or dict to model. + + :param data: dict, list. + :type data: dict | list + :param klass: class literal. + :return: model object. + """ + instance = klass() + + if not instance.swagger_types: + return data + + for attr, attr_type in six.iteritems(instance.swagger_types): + if data is not None \ + and instance.attribute_map[attr] in data \ + and isinstance(data, (list, dict)): + value = data[instance.attribute_map[attr]] + setattr(instance, attr, _deserialize(value, attr_type)) + + return instance + + +def _deserialize_list(data, boxed_type): + """Deserializes a list and its elements. + + :param data: list to deserialize. + :type data: list + :param boxed_type: class literal. + + :return: deserialized list. + :rtype: list + """ + return [_deserialize(sub_data, boxed_type) + for sub_data in data] + + +def _deserialize_dict(data, boxed_type): + """Deserializes a dict and its elements. + + :param data: dict to deserialize. + :type data: dict + :param boxed_type: class literal. + + :return: deserialized dict. + :rtype: dict + """ + return {k: _deserialize(v, boxed_type) + for k, v in six.iteritems(data)} From 305c223f257cd8b25e7c67cbc8a44a8e2459004b Mon Sep 17 00:00:00 2001 From: Hugo Mercado Date: Sun, 28 Apr 2019 15:38:06 -0500 Subject: [PATCH 03/11] added handlebars templates for python flask. --- .../pythonFlaskConnexion/Dockerfile.mustache | 31 ++++ .../pythonFlaskConnexion/README.mustache | 60 +++++++ .../pythonFlaskConnexion/__init__.mustache | 0 .../__init__model.mustache | 7 + .../__init__test.mustache | 16 ++ .../pythonFlaskConnexion/__main__.mustache | 21 +++ .../pythonFlaskConnexion/base_model_.mustache | 73 ++++++++ .../pythonFlaskConnexion/controller.mustache | 112 ++++++++++++ .../controller_test.mustache | 51 ++++++ .../dockerignore.mustache | 72 ++++++++ .../pythonFlaskConnexion/encoder.mustache | 20 +++ .../pythonFlaskConnexion/git_push.sh.mustache | 52 ++++++ .../pythonFlaskConnexion/gitignore.mustache | 64 +++++++ .../pythonFlaskConnexion/model.mustache | 161 ++++++++++++++++++ .../pythonFlaskConnexion/param_type.mustache | 1 + .../requirements.mustache | 6 + .../pythonFlaskConnexion/setup.mustache | 35 ++++ .../pythonFlaskConnexion/swagger.mustache | 1 + .../test-requirements.mustache | 6 + .../pythonFlaskConnexion/tox.mustache | 10 ++ .../pythonFlaskConnexion/travis.mustache | 16 ++ .../pythonFlaskConnexion/util.mustache | 141 +++++++++++++++ 22 files changed, 956 insertions(+) create mode 100644 src/main/resources/handlebars/pythonFlaskConnexion/Dockerfile.mustache create mode 100644 src/main/resources/handlebars/pythonFlaskConnexion/README.mustache create mode 100644 src/main/resources/handlebars/pythonFlaskConnexion/__init__.mustache create mode 100644 src/main/resources/handlebars/pythonFlaskConnexion/__init__model.mustache create mode 100644 src/main/resources/handlebars/pythonFlaskConnexion/__init__test.mustache create mode 100644 src/main/resources/handlebars/pythonFlaskConnexion/__main__.mustache create mode 100644 src/main/resources/handlebars/pythonFlaskConnexion/base_model_.mustache create mode 100644 src/main/resources/handlebars/pythonFlaskConnexion/controller.mustache create mode 100644 src/main/resources/handlebars/pythonFlaskConnexion/controller_test.mustache create mode 100644 src/main/resources/handlebars/pythonFlaskConnexion/dockerignore.mustache create mode 100644 src/main/resources/handlebars/pythonFlaskConnexion/encoder.mustache create mode 100755 src/main/resources/handlebars/pythonFlaskConnexion/git_push.sh.mustache create mode 100644 src/main/resources/handlebars/pythonFlaskConnexion/gitignore.mustache create mode 100644 src/main/resources/handlebars/pythonFlaskConnexion/model.mustache create mode 100644 src/main/resources/handlebars/pythonFlaskConnexion/param_type.mustache create mode 100644 src/main/resources/handlebars/pythonFlaskConnexion/requirements.mustache create mode 100644 src/main/resources/handlebars/pythonFlaskConnexion/setup.mustache create mode 100644 src/main/resources/handlebars/pythonFlaskConnexion/swagger.mustache create mode 100644 src/main/resources/handlebars/pythonFlaskConnexion/test-requirements.mustache create mode 100644 src/main/resources/handlebars/pythonFlaskConnexion/tox.mustache create mode 100644 src/main/resources/handlebars/pythonFlaskConnexion/travis.mustache create mode 100644 src/main/resources/handlebars/pythonFlaskConnexion/util.mustache diff --git a/src/main/resources/handlebars/pythonFlaskConnexion/Dockerfile.mustache b/src/main/resources/handlebars/pythonFlaskConnexion/Dockerfile.mustache new file mode 100644 index 0000000000..f040d41ad6 --- /dev/null +++ b/src/main/resources/handlebars/pythonFlaskConnexion/Dockerfile.mustache @@ -0,0 +1,31 @@ +{{#supportPython2}} +FROM python:2-alpine +{{/supportPython2}} +{{^supportPython2}} +FROM python:3-alpine +{{/supportPython2}} + +RUN mkdir -p /usr/src/app +WORKDIR /usr/src/app + +COPY requirements.txt /usr/src/app/ + +{{#supportPython2}} +RUN pip install --no-cache-dir -r requirements.txt +{{/supportPython2}} +{{^supportPython2}} +RUN pip3 install --no-cache-dir -r requirements.txt +{{/supportPython2}} + +COPY . /usr/src/app + +EXPOSE {{serverPort}} + +{{#supportPython2}} +ENTRYPOINT ["python"] +{{/supportPython2}} +{{^supportPython2}} +ENTRYPOINT ["python3"] +{{/supportPython2}} + +CMD ["-m", "{{packageName}}"] \ No newline at end of file diff --git a/src/main/resources/handlebars/pythonFlaskConnexion/README.mustache b/src/main/resources/handlebars/pythonFlaskConnexion/README.mustache new file mode 100644 index 0000000000..b73b9e311b --- /dev/null +++ b/src/main/resources/handlebars/pythonFlaskConnexion/README.mustache @@ -0,0 +1,60 @@ +# Swagger generated server + +## Overview +This server was generated by the [swagger-codegen](https://github.com/swagger-api/swagger-codegen) project. By using the +[OpenAPI-Spec](https://github.com/swagger-api/swagger-core/wiki) from a remote server, you can easily generate a server stub. This +is an example of building a swagger-enabled Flask server. + +This example uses the [Connexion](https://github.com/zalando/connexion) library on top of Flask. + +## Requirements +{{#supportPython2}} +Python 2.7+ +{{/supportPython2}} +{{^supportPython2}} +Python 3.5.2+ +{{/supportPython2}} + +## Usage +To run the server, please execute the following from the root directory: + +``` +{{#supportPython2}} +pip install -r requirements.txt +python -m {{packageName}} +{{/supportPython2}} +{{^supportPython2}} +pip3 install -r requirements.txt +python3 -m {{packageName}} +{{/supportPython2}} +``` + +and open your browser to here: + +``` +http://localhost:{{serverPort}}{{contextPath}}/ui/ +``` + +Your Swagger definition lives here: + +``` +http://localhost:{{serverPort}}{{contextPath}}/swagger.json +``` + +To launch the integration tests, use tox: +``` +sudo pip install tox +tox +``` + +## Running with Docker + +To run the server on a Docker container, please execute the following from the root directory: + +```bash +# building the image +docker build -t {{packageName}} . + +# starting up a container +docker run -p {{serverPort}}:{{serverPort}} {{packageName}} +``` \ No newline at end of file diff --git a/src/main/resources/handlebars/pythonFlaskConnexion/__init__.mustache b/src/main/resources/handlebars/pythonFlaskConnexion/__init__.mustache new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/main/resources/handlebars/pythonFlaskConnexion/__init__model.mustache b/src/main/resources/handlebars/pythonFlaskConnexion/__init__model.mustache new file mode 100644 index 0000000000..98b56e3d15 --- /dev/null +++ b/src/main/resources/handlebars/pythonFlaskConnexion/__init__model.mustache @@ -0,0 +1,7 @@ +# coding: utf-8 + +# flake8: noqa +from __future__ import absolute_import +# import models into model package +{{#models}}{{#model}}from {{modelPackage}}.{{classFilename}} import {{classname}}{{/model}} +{{/models}} \ No newline at end of file diff --git a/src/main/resources/handlebars/pythonFlaskConnexion/__init__test.mustache b/src/main/resources/handlebars/pythonFlaskConnexion/__init__test.mustache new file mode 100644 index 0000000000..9cea80bc63 --- /dev/null +++ b/src/main/resources/handlebars/pythonFlaskConnexion/__init__test.mustache @@ -0,0 +1,16 @@ +import logging + +import connexion +from flask_testing import TestCase + +from {{packageName}}.encoder import JSONEncoder + + +class BaseTestCase(TestCase): + + def create_app(self): + logging.getLogger('connexion.operation').setLevel('ERROR') + app = connexion.App(__name__, specification_dir='../swagger/') + app.app.json_encoder = JSONEncoder + app.add_api('swagger.yaml') + return app.app diff --git a/src/main/resources/handlebars/pythonFlaskConnexion/__main__.mustache b/src/main/resources/handlebars/pythonFlaskConnexion/__main__.mustache new file mode 100644 index 0000000000..1075634722 --- /dev/null +++ b/src/main/resources/handlebars/pythonFlaskConnexion/__main__.mustache @@ -0,0 +1,21 @@ +{{#supportPython2}} +#!/usr/bin/env python +{{/supportPython2}} +{{^supportPython2}} +#!/usr/bin/env python3 +{{/supportPython2}} + +import connexion + +from {{packageName}} import encoder + + +def main(): + app = connexion.App(__name__, specification_dir='./swagger/') + app.app.json_encoder = encoder.JSONEncoder + app.add_api('swagger.yaml', arguments={'title': '{{appName}}'}) + app.run(port={{serverPort}}) + + +if __name__ == '__main__': + main() diff --git a/src/main/resources/handlebars/pythonFlaskConnexion/base_model_.mustache b/src/main/resources/handlebars/pythonFlaskConnexion/base_model_.mustache new file mode 100644 index 0000000000..54517a06d5 --- /dev/null +++ b/src/main/resources/handlebars/pythonFlaskConnexion/base_model_.mustache @@ -0,0 +1,73 @@ +import pprint + +import six +{{^supportPython2}} +import typing +{{/supportPython2}} + +from {{packageName}} import util +{{^supportPython2}} + +T = typing.TypeVar('T') +{{/supportPython2}} + + +class Model(object): + # swaggerTypes: The key is attribute name and the + # value is attribute type. + swagger_types = {} + + # attributeMap: The key is attribute name and the + # value is json key in definition. + attribute_map = {} + + @classmethod + def from_dict(cls{{^supportPython2}}: typing.Type[T]{{/supportPython2}}, dikt){{^supportPython2}} -> T{{/supportPython2}}: + """Returns the dict as a model""" + return util.deserialize_model(dikt, cls) + + def to_dict(self): + """Returns the model properties as a dict + + :rtype: dict + """ + result = {} + + for attr, _ in six.iteritems(self.swagger_types): + value = getattr(self, attr) + if isinstance(value, list): + result[attr] = list(map( + lambda x: x.to_dict() if hasattr(x, "to_dict") else x, + value + )) + elif hasattr(value, "to_dict"): + result[attr] = value.to_dict() + elif isinstance(value, dict): + result[attr] = dict(map( + lambda item: (item[0], item[1].to_dict()) + if hasattr(item[1], "to_dict") else item, + value.items() + )) + else: + result[attr] = value + + return result + + def to_str(self): + """Returns the string representation of the model + + :rtype: str + """ + 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 diff --git a/src/main/resources/handlebars/pythonFlaskConnexion/controller.mustache b/src/main/resources/handlebars/pythonFlaskConnexion/controller.mustache new file mode 100644 index 0000000000..243da33e57 --- /dev/null +++ b/src/main/resources/handlebars/pythonFlaskConnexion/controller.mustache @@ -0,0 +1,112 @@ +import connexion +import six + +{{#imports}}{{import}} # noqa: E501 +{{/imports}} +from {{packageName}} import util +{{#operations}} +{{#operation}} + + +def {{operationId}}({{#allParams}}{{paramName}}{{^required}}=None{{/required}}{{#hasMore}}, {{/hasMore}}{{/allParams}}): # noqa: E501 + """{{#summary}}{{.}}{{/summary}}{{^summary}}{{operationId}}{{/summary}} + + {{#notes}}{{.}}{{/notes}} # noqa: E501 + + {{#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}} + + :rtype: {{#returnType}}{{.}}{{/returnType}}{{^returnType}}None{{/returnType}} + """ + {{#allParams}} + {{^isContainer}} + {{#isDate}} + {{paramName}} = util.deserialize_date({{paramName}}) + {{/isDate}} + {{#isDateTime}} + {{paramName}} = util.deserialize_datetime({{paramName}}) + {{/isDateTime}} + {{^isPrimitiveType}} + {{^isFile}} + {{^isUuid}} + if connexion.request.is_json: + {{paramName}} = {{baseType}}.from_dict(connexion.request.get_json()) # noqa: E501 + {{/isUuid}} + {{/isFile}} + {{/isPrimitiveType}} + {{/isContainer}} + {{#isListContainer}} + {{#items}} + {{#isDate}} + if connexion.request.is_json: + {{paramName}} = [util.deserialize_date(s) for s in connexion.request.get_json()] # noqa: E501 + {{/isDate}} + {{#isDateTime}} + if connexion.request.is_json: + {{paramName}} = [util.deserialize_datetime(s) for s in connexion.request.get_json()] # noqa: E501 + {{/isDateTime}} + {{#complexType}} + if connexion.request.is_json: + {{paramName}} = [{{complexType}}.from_dict(d) for d in connexion.request.get_json()] # noqa: E501 + {{/complexType}} + {{/items}} + {{/isListContainer}} + {{#isMapContainer}} + {{#items}} + {{#isDate}} + if connexion.request.is_json: + {{paramName}} = {k: util.deserialize_date(v) for k, v in six.iteritems(connexion.request.get_json())} # noqa: E501 + {{/isDate}} + {{#isDateTime}} + if connexion.request.is_json: + {{paramName}} = {k: util.deserialize_datetime(v) for k, v in six.iteritems(connexion.request.get_json())} # noqa: E501 + {{/isDateTime}} + {{#complexType}} + if connexion.request.is_json: + {{paramName}} = {k: {{baseType}}.from_dict(v) for k, v in six.iteritems(connexion.request.get_json())} # noqa: E501 + {{/complexType}} + {{/items}} + {{/isMapContainer}} + {{/allParams}} + return 'do some magic!' +{{/operation}} +{{/operations}} diff --git a/src/main/resources/handlebars/pythonFlaskConnexion/controller_test.mustache b/src/main/resources/handlebars/pythonFlaskConnexion/controller_test.mustache new file mode 100644 index 0000000000..a41b12f2c2 --- /dev/null +++ b/src/main/resources/handlebars/pythonFlaskConnexion/controller_test.mustache @@ -0,0 +1,51 @@ +# coding: utf-8 + +from __future__ import absolute_import + +from flask import json +from six import BytesIO + +{{#imports}}{{import}} # noqa: E501 +{{/imports}} +from {{packageName}}.test import BaseTestCase + + +class {{#operations}}Test{{classname}}(BaseTestCase): + """{{classname}} integration test stubs""" + + {{#operation}} + def test_{{operationId}}(self): + """Test case for {{{operationId}}} + + {{{summary}}} + """ + {{#bodyParam}} + {{paramName}} = {{{example}}} + {{/bodyParam}} + {{#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}} + {{#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}}, + data=json.dumps({{paramName}}){{^consumes}}, + content_type='application/json'{{/consumes}}{{/bodyParam}}{{#headerParams}}{{#-first}}, + headers=headers{{/-first}}{{/headerParams}}{{#formParams}}{{#-first}}, + data=data{{/-first}}{{/formParams}}{{#consumes}}{{#-first}}, + content_type='{{{mediaType}}}'{{/-first}}{{/consumes}}{{#queryParams}}{{#-first}}, + query_string=query_string{{/-first}}{{/queryParams}}) + self.assert200(response, + 'Response body is : ' + response.data.decode('utf-8')) + + {{/operation}} +{{/operations}} + +if __name__ == '__main__': + import unittest + unittest.main() diff --git a/src/main/resources/handlebars/pythonFlaskConnexion/dockerignore.mustache b/src/main/resources/handlebars/pythonFlaskConnexion/dockerignore.mustache new file mode 100644 index 0000000000..cdd823e64e --- /dev/null +++ b/src/main/resources/handlebars/pythonFlaskConnexion/dockerignore.mustache @@ -0,0 +1,72 @@ +.travis.yaml +.swagger-codegen-ignore +README.md +tox.ini +git_push.sh +test-requirements.txt +setup.py + +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +env/ +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +*.egg-info/ +.installed.cfg +*.egg + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*,cover +.hypothesis/ +venv/ +.python-version + +# Translations +*.mo +*.pot + +# Django stuff: +*.log + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +#Ipython Notebook +.ipynb_checkpoints diff --git a/src/main/resources/handlebars/pythonFlaskConnexion/encoder.mustache b/src/main/resources/handlebars/pythonFlaskConnexion/encoder.mustache new file mode 100644 index 0000000000..e303a0e41a --- /dev/null +++ b/src/main/resources/handlebars/pythonFlaskConnexion/encoder.mustache @@ -0,0 +1,20 @@ +from connexion.apps.flask_app import FlaskJSONEncoder +import six + +from {{modelPackage}}.base_model_ import Model + + +class JSONEncoder(FlaskJSONEncoder): + include_nulls = False + + def default(self, o): + if isinstance(o, Model): + dikt = {} + for attr, _ in six.iteritems(o.swagger_types): + value = getattr(o, attr) + if value is None and not self.include_nulls: + continue + attr = o.attribute_map[attr] + dikt[attr] = value + return dikt + return FlaskJSONEncoder.default(self, o) diff --git a/src/main/resources/handlebars/pythonFlaskConnexion/git_push.sh.mustache b/src/main/resources/handlebars/pythonFlaskConnexion/git_push.sh.mustache new file mode 100755 index 0000000000..f65b794638 --- /dev/null +++ b/src/main/resources/handlebars/pythonFlaskConnexion/git_push.sh.mustache @@ -0,0 +1,52 @@ +#!/bin/sh +# ref: https://help.github.com/articles/adding-an-existing-project-to-github-using-the-command-line/ +# +# Usage example: /bin/sh ./git_push.sh wing328 swagger-petstore-perl "minor update" + +git_user_id=$1 +git_repo_id=$2 +release_note=$3 + +if [ "$git_user_id" = "" ]; then + git_user_id="{{{gitUserId}}}" + echo "[INFO] No command line input provided. Set \$git_user_id to $git_user_id" +fi + +if [ "$git_repo_id" = "" ]; then + git_repo_id="{{{gitRepoId}}}" + echo "[INFO] No command line input provided. Set \$git_repo_id to $git_repo_id" +fi + +if [ "$release_note" = "" ]; then + release_note="{{{releaseNote}}}" + echo "[INFO] No command line input provided. Set \$release_note to $release_note" +fi + +# Initialize the local directory as a Git repository +git init + +# Adds the files in the local repository and stages them for commit. +git add . + +# Commits the tracked changes and prepares them to be pushed to a remote repository. +git commit -m "$release_note" + +# Sets the new remote +git_remote=`git remote` +if [ "$git_remote" = "" ]; then # git remote not defined + + if [ "$GIT_TOKEN" = "" ]; then + echo "[INFO] \$GIT_TOKEN (environment variable) is not set. Using the git credential in your environment." + git remote add origin https://github.com/${git_user_id}/${git_repo_id}.git + else + git remote add origin https://${git_user_id}:${GIT_TOKEN}@github.com/${git_user_id}/${git_repo_id}.git + fi + +fi + +git pull origin master + +# Pushes (Forces) the changes in the local repository up to the remote repository +echo "Git pushing to https://github.com/${git_user_id}/${git_repo_id}.git" +git push origin master 2>&1 | grep -v 'To https' + diff --git a/src/main/resources/handlebars/pythonFlaskConnexion/gitignore.mustache b/src/main/resources/handlebars/pythonFlaskConnexion/gitignore.mustache new file mode 100644 index 0000000000..a655050c26 --- /dev/null +++ b/src/main/resources/handlebars/pythonFlaskConnexion/gitignore.mustache @@ -0,0 +1,64 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +env/ +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +*.egg-info/ +.installed.cfg +*.egg + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*,cover +.hypothesis/ +venv/ +.python-version + +# Translations +*.mo +*.pot + +# Django stuff: +*.log + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +#Ipython Notebook +.ipynb_checkpoints diff --git a/src/main/resources/handlebars/pythonFlaskConnexion/model.mustache b/src/main/resources/handlebars/pythonFlaskConnexion/model.mustache new file mode 100644 index 0000000000..7bb398f9f7 --- /dev/null +++ b/src/main/resources/handlebars/pythonFlaskConnexion/model.mustache @@ -0,0 +1,161 @@ +# coding: utf-8 + +from __future__ import absolute_import +from datetime import date, datetime # noqa: F401 + +from typing import List, Dict # noqa: F401 + +from {{modelPackage}}.base_model_ import Model +{{#imports}}{{import}} # noqa: F401,E501 +{{/imports}} +from {{packageName}} import util + + +{{#models}} +{{#model}} +class {{classname}}(Model): + """NOTE: This class is auto generated by the swagger code generator program. + + Do not edit the class manually. + """{{#allowableValues}} + + """ + allowed enum values + """ +{{#enumVars}} + {{name}} = {{{value}}}{{^-last}} +{{/-last}} +{{/enumVars}}{{/allowableValues}} + + def __init__(self{{#vars}}, {{name}}{{^supportPython2}}: {{datatype}}{{/supportPython2}}={{#defaultValue}}{{{defaultValue}}}{{/defaultValue}}{{^defaultValue}}None{{/defaultValue}}{{/vars}}): # noqa: E501 + """{{classname}} - a model defined in Swagger + + {{#vars}} + :param {{name}}: The {{name}} of this {{classname}}. # noqa: E501 + :type {{name}}: {{datatype}} + {{/vars}} + """ + self.swagger_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){{^supportPython2}} -> '{{classname}}'{{/supportPython2}}: + """Returns the dict as a model + + :param dikt: A dict. + :type: dict + :return: The {{name}} of this {{classname}}. # noqa: E501 + :rtype: {{classname}} + """ + return util.deserialize_model(dikt, cls){{#vars}}{{#-first}} + +{{/-first}} + @property + def {{name}}(self){{^supportPython2}} -> {{datatype}}{{/supportPython2}}: + """Gets the {{name}} of this {{classname}}. + + {{#description}} + {{{description}}} # noqa: E501 + {{/description}} + + :return: The {{name}} of this {{classname}}. + :rtype: {{datatype}} + """ + return self._{{name}} + + @{{name}}.setter + def {{name}}(self, {{name}}{{^supportPython2}}: {{datatype}}{{/supportPython2}}): + """Sets the {{name}} of this {{classname}}. + + {{#description}} + {{{description}}} # noqa: E501 + {{/description}} + + :param {{name}}: The {{name}} of this {{classname}}. + :type {{name}}: {{datatype}} + """ +{{#isEnum}} + allowed_values = [{{#allowableValues}}{{#values}}"{{{this}}}"{{^-last}}, {{/-last}}{{/values}}{{/allowableValues}}] # noqa: E501 +{{#isContainer}} +{{#isListContainer}} + if not set({{{name}}}).issubset(set(allowed_values)): + raise ValueError( + "Invalid values for `{{{name}}}` [{0}], must be a subset of [{1}]" # noqa: E501 + .format(", ".join(map(str, set({{{name}}}) - set(allowed_values))), # noqa: E501 + ", ".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}]" # noqa: E501 + .format(", ".join(map(str, set({{{name}}}.keys()) - set(allowed_values))), # noqa: E501 + ", ".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`") # noqa: E501 +{{/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}}`") # noqa: E501 +{{/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}}`") # noqa: E501 +{{/minLength}} +{{#maximum}} + if {{name}} is not None and {{name}} >{{#exclusiveMaximum}}={{/exclusiveMaximum}} {{maximum}}: # noqa: E501 + raise ValueError("Invalid value for `{{name}}`, must be a value less than {{^exclusiveMaximum}}or equal to {{/exclusiveMaximum}}`{{maximum}}`") # noqa: E501 +{{/maximum}} +{{#minimum}} + if {{name}} is not None and {{name}} <{{#exclusiveMinimum}}={{/exclusiveMinimum}} {{minimum}}: # noqa: E501 + raise ValueError("Invalid value for `{{name}}`, must be a value greater than {{^exclusiveMinimum}}or equal to {{/exclusiveMinimum}}`{{minimum}}`") # noqa: E501 +{{/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}}): # noqa: E501 + raise ValueError("Invalid value for `{{name}}`, must be a follow pattern or equal to `{{{pattern}}}`") # noqa: E501 +{{/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}}`") # noqa: E501 +{{/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}}`") # noqa: E501 +{{/minItems}} +{{/hasValidation}} +{{/isEnum}} + + self._{{name}} = {{name}}{{^-last}} + +{{/-last}} +{{/vars}} + +{{/model}} +{{/models}} \ No newline at end of file diff --git a/src/main/resources/handlebars/pythonFlaskConnexion/param_type.mustache b/src/main/resources/handlebars/pythonFlaskConnexion/param_type.mustache new file mode 100644 index 0000000000..21e7e07ec5 --- /dev/null +++ b/src/main/resources/handlebars/pythonFlaskConnexion/param_type.mustache @@ -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}} \ No newline at end of file diff --git a/src/main/resources/handlebars/pythonFlaskConnexion/requirements.mustache b/src/main/resources/handlebars/pythonFlaskConnexion/requirements.mustache new file mode 100644 index 0000000000..04042d4b1d --- /dev/null +++ b/src/main/resources/handlebars/pythonFlaskConnexion/requirements.mustache @@ -0,0 +1,6 @@ +connexion == 1.1.15 +python_dateutil == 2.6.0 +{{#supportPython2}} +typing == 3.5.2.2 +{{/supportPython2}} +setuptools >= 21.0.0 diff --git a/src/main/resources/handlebars/pythonFlaskConnexion/setup.mustache b/src/main/resources/handlebars/pythonFlaskConnexion/setup.mustache new file mode 100644 index 0000000000..56f8bc1ec1 --- /dev/null +++ b/src/main/resources/handlebars/pythonFlaskConnexion/setup.mustache @@ -0,0 +1,35 @@ +# coding: utf-8 + +import sys +from setuptools import setup, find_packages + +NAME = "{{packageName}}" +VERSION = "{{packageVersion}}" +{{#apiInfo}}{{#apis}}{{^hasMore}} +# To install the library, run the following +# +# python setup.py install +# +# prerequisite: setuptools +# http://pypi.python.org/pypi/setuptools + +REQUIRES = ["connexion"] + +setup( + name=NAME, + version=VERSION, + description="{{appName}}", + author_email="{{infoEmail}}", + url="{{packageUrl}}", + keywords=["Swagger", "{{appName}}"], + install_requires=REQUIRES, + packages=find_packages(), + package_data={'': ['swagger/swagger.yaml']}, + include_package_data=True, + entry_points={ + 'console_scripts': ['{{packageName}}={{packageName}}.__main__:main']}, + long_description="""\ + {{appDescription}} + """ +) +{{/hasMore}}{{/apis}}{{/apiInfo}} diff --git a/src/main/resources/handlebars/pythonFlaskConnexion/swagger.mustache b/src/main/resources/handlebars/pythonFlaskConnexion/swagger.mustache new file mode 100644 index 0000000000..51560926bb --- /dev/null +++ b/src/main/resources/handlebars/pythonFlaskConnexion/swagger.mustache @@ -0,0 +1 @@ +{{{swagger-yaml}}} \ No newline at end of file diff --git a/src/main/resources/handlebars/pythonFlaskConnexion/test-requirements.mustache b/src/main/resources/handlebars/pythonFlaskConnexion/test-requirements.mustache new file mode 100644 index 0000000000..7f8d96e6b4 --- /dev/null +++ b/src/main/resources/handlebars/pythonFlaskConnexion/test-requirements.mustache @@ -0,0 +1,6 @@ +flask_testing==0.6.1 +coverage>=4.0.3 +nose>=1.3.7 +pluggy>=0.3.1 +py>=1.4.31 +randomize>=0.13 diff --git a/src/main/resources/handlebars/pythonFlaskConnexion/tox.mustache b/src/main/resources/handlebars/pythonFlaskConnexion/tox.mustache new file mode 100644 index 0000000000..3efa994317 --- /dev/null +++ b/src/main/resources/handlebars/pythonFlaskConnexion/tox.mustache @@ -0,0 +1,10 @@ +[tox] +envlist = {{#supportPython2}}py27, {{/supportPython2}}py35 + +[testenv] +deps=-r{toxinidir}/requirements.txt + -r{toxinidir}/test-requirements.txt + +commands= + nosetests \ + [] \ No newline at end of file diff --git a/src/main/resources/handlebars/pythonFlaskConnexion/travis.mustache b/src/main/resources/handlebars/pythonFlaskConnexion/travis.mustache new file mode 100644 index 0000000000..167826e42b --- /dev/null +++ b/src/main/resources/handlebars/pythonFlaskConnexion/travis.mustache @@ -0,0 +1,16 @@ +# ref: https://docs.travis-ci.com/user/languages/python +language: python +python: +{{#supportPython2}} + - "2.7" +{{/supportPython2}} + - "3.2" + - "3.3" + - "3.4" + - "3.5" + #- "3.5-dev" # 3.5 development branch + #- "nightly" # points to the latest development branch e.g. 3.6-dev +# command to install dependencies +install: "pip install -r requirements.txt" +# command to run tests +script: nosetests diff --git a/src/main/resources/handlebars/pythonFlaskConnexion/util.mustache b/src/main/resources/handlebars/pythonFlaskConnexion/util.mustache new file mode 100644 index 0000000000..527d1424c3 --- /dev/null +++ b/src/main/resources/handlebars/pythonFlaskConnexion/util.mustache @@ -0,0 +1,141 @@ +import datetime + +import six +import typing + + +def _deserialize(data, klass): + """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 six.integer_types or klass in (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): + """Deserializes to primitive type. + + :param data: data to deserialize. + :param klass: class literal. + + :return: int, long, float, str, bool. + :rtype: int | long | float | str | bool + """ + try: + value = klass(data) + except UnicodeEncodeError: + value = six.u(data) + except TypeError: + value = data + return value + + +def _deserialize_object(value): + """Return a original value. + + :return: object. + """ + return value + + +def deserialize_date(string): + """Deserializes string to date. + + :param string: str. + :type string: str + :return: date. + :rtype: date + """ + try: + from dateutil.parser import parse + return parse(string).date() + except ImportError: + return string + + +def deserialize_datetime(string): + """Deserializes string to datetime. + + The string should be in iso8601 datetime format. + + :param string: str. + :type string: str + :return: datetime. + :rtype: datetime + """ + try: + from dateutil.parser import parse + return parse(string) + except ImportError: + return string + + +def deserialize_model(data, klass): + """Deserializes list or dict to model. + + :param data: dict, list. + :type data: dict | list + :param klass: class literal. + :return: model object. + """ + instance = klass() + + if not instance.swagger_types: + return data + + for attr, attr_type in six.iteritems(instance.swagger_types): + if data is not None \ + and instance.attribute_map[attr] in data \ + and isinstance(data, (list, dict)): + value = data[instance.attribute_map[attr]] + setattr(instance, attr, _deserialize(value, attr_type)) + + return instance + + +def _deserialize_list(data, boxed_type): + """Deserializes a list and its elements. + + :param data: list to deserialize. + :type data: list + :param boxed_type: class literal. + + :return: deserialized list. + :rtype: list + """ + return [_deserialize(sub_data, boxed_type) + for sub_data in data] + + +def _deserialize_dict(data, boxed_type): + """Deserializes a dict and its elements. + + :param data: dict to deserialize. + :type data: dict + :param boxed_type: class literal. + + :return: deserialized dict. + :rtype: dict + """ + return {k: _deserialize(v, boxed_type) + for k, v in six.iteritems(data)} From e4594fcba0acc668b7f34dddc3dd4e42731e756f Mon Sep 17 00:00:00 2001 From: Hugo Mercado Date: Sun, 28 Apr 2019 17:53:10 -0500 Subject: [PATCH 04/11] updated templates copied templates to work in handlebars format. --- .../python/PythonFlaskConnexionCodegen.java | 2 +- .../controller_test.mustache | 18 +++++++-------- .../pythonFlaskConnexion/model.mustache | 22 +++++++++---------- .../pythonFlaskConnexion/swagger.mustache | 2 +- .../pythonFlaskConnexion/swagger.mustache | 2 +- 5 files changed, 23 insertions(+), 23 deletions(-) diff --git a/src/main/java/io/swagger/codegen/v3/generators/python/PythonFlaskConnexionCodegen.java b/src/main/java/io/swagger/codegen/v3/generators/python/PythonFlaskConnexionCodegen.java index b7542e26ab..11698fb60a 100644 --- a/src/main/java/io/swagger/codegen/v3/generators/python/PythonFlaskConnexionCodegen.java +++ b/src/main/java/io/swagger/codegen/v3/generators/python/PythonFlaskConnexionCodegen.java @@ -389,7 +389,7 @@ public Map postProcessSupportingFileData(Map obj OpenAPI swagger = (OpenAPI) objs.get("openapi"); if(swagger != null) { try { - objs.put("opennapi-yaml", Yaml.mapper().writeValueAsString(swagger)); + objs.put("openapi-yaml", Yaml.mapper().writeValueAsString(swagger)); } catch (JsonProcessingException e) { LOGGER.error(e.getMessage(), e); } diff --git a/src/main/resources/handlebars/pythonFlaskConnexion/controller_test.mustache b/src/main/resources/handlebars/pythonFlaskConnexion/controller_test.mustache index a41b12f2c2..2f3b4c4148 100644 --- a/src/main/resources/handlebars/pythonFlaskConnexion/controller_test.mustache +++ b/src/main/resources/handlebars/pythonFlaskConnexion/controller_test.mustache @@ -23,23 +23,23 @@ class {{#operations}}Test{{classname}}(BaseTestCase): {{paramName}} = {{{example}}} {{/bodyParam}} {{#queryParams}} - {{#-first}}query_string = [{{/-first}}{{^-first}} {{/-first}}('{{paramName}}', {{{example}}}){{#hasMore}},{{/hasMore}}{{#-last}}]{{/-last}} + {{#@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}} + {{#@first}}headers = [{{/@first}}{{^@first}} {{/@first}}('{{paramName}}', {{{example}}}){{#hasMore}},{{/hasMore}}{{#@last}}]{{/@last}} {{/headerParams}} {{#formParams}} - {{#-first}}data = dict({{/-first}}{{^-first}} {{/-first}}{{paramName}}={{{example}}}{{#hasMore}},{{/hasMore}}{{#-last}}){{/-last}} + {{#@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}}, + '{{#contextPath}}{{{.}}}{{/contextPath}}{{{path}}}'{{#pathParams}}{{#@first}}.format({{/@first}}{{paramName}}={{{example}}}{{#hasMore}}, {{/hasMore}}{{^hasMore}}){{/hasMore}}{{/pathParams}}, method='{{httpMethod}}'{{#bodyParam}}, data=json.dumps({{paramName}}){{^consumes}}, - content_type='application/json'{{/consumes}}{{/bodyParam}}{{#headerParams}}{{#-first}}, - headers=headers{{/-first}}{{/headerParams}}{{#formParams}}{{#-first}}, - data=data{{/-first}}{{/formParams}}{{#consumes}}{{#-first}}, - content_type='{{{mediaType}}}'{{/-first}}{{/consumes}}{{#queryParams}}{{#-first}}, - query_string=query_string{{/-first}}{{/queryParams}}) + content_type='application/json'{{/consumes}}{{/bodyParam}}{{#headerParams}}{{#@first}}, + headers=headers{{/@first}}{{/headerParams}}{{#formParams}}{{#@first}}, + data=data{{/@first}}{{/formParams}}{{#consumes}}{{#@first}}, + content_type='{{{mediaType}}}'{{/@first}}{{/consumes}}{{#queryParams}}{{#@first}}, + query_string=query_string{{/@first}}{{/queryParams}}) self.assert200(response, 'Response body is : ' + response.data.decode('utf-8')) diff --git a/src/main/resources/handlebars/pythonFlaskConnexion/model.mustache b/src/main/resources/handlebars/pythonFlaskConnexion/model.mustache index 7bb398f9f7..2f08ab7c38 100644 --- a/src/main/resources/handlebars/pythonFlaskConnexion/model.mustache +++ b/src/main/resources/handlebars/pythonFlaskConnexion/model.mustache @@ -23,8 +23,8 @@ class {{classname}}(Model): allowed enum values """ {{#enumVars}} - {{name}} = {{{value}}}{{^-last}} -{{/-last}} + {{name}} = {{{value}}}{{^@last}} +{{/@last}} {{/enumVars}}{{/allowableValues}} def __init__(self{{#vars}}, {{name}}{{^supportPython2}}: {{datatype}}{{/supportPython2}}={{#defaultValue}}{{{defaultValue}}}{{/defaultValue}}{{^defaultValue}}None{{/defaultValue}}{{/vars}}): # noqa: E501 @@ -46,8 +46,8 @@ class {{classname}}(Model): '{{name}}': '{{baseName}}'{{#hasMore}},{{/hasMore}} {{/vars}} } -{{#vars}}{{#-first}} -{{/-first}} +{{#vars}}{{#@first}} +{{/@first}} self._{{name}} = {{name}} {{/vars}} @@ -60,9 +60,9 @@ class {{classname}}(Model): :return: The {{name}} of this {{classname}}. # noqa: E501 :rtype: {{classname}} """ - return util.deserialize_model(dikt, cls){{#vars}}{{#-first}} + return util.deserialize_model(dikt, cls){{#vars}}{{#@first}} -{{/-first}} +{{/@first}} @property def {{name}}(self){{^supportPython2}} -> {{datatype}}{{/supportPython2}}: """Gets the {{name}} of this {{classname}}. @@ -88,7 +88,7 @@ class {{classname}}(Model): :type {{name}}: {{datatype}} """ {{#isEnum}} - allowed_values = [{{#allowableValues}}{{#values}}"{{{this}}}"{{^-last}}, {{/-last}}{{/values}}{{/allowableValues}}] # noqa: E501 + allowed_values = [{{#allowableValues}}{{#values}}"{{{this}}}"{{^@last}}, {{/@last}}{{/values}}{{/allowableValues}}] # noqa: E501 {{#isContainer}} {{#isListContainer}} if not set({{{name}}}).issubset(set(allowed_values)): @@ -138,7 +138,7 @@ class {{classname}}(Model): raise ValueError("Invalid value for `{{name}}`, must be a value greater than {{^exclusiveMinimum}}or equal to {{/exclusiveMinimum}}`{{minimum}}`") # noqa: E501 {{/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}}): # noqa: E501 + 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}}): # noqa: E501 raise ValueError("Invalid value for `{{name}}`, must be a follow pattern or equal to `{{{pattern}}}`") # noqa: E501 {{/pattern}} {{#maxItems}} @@ -152,10 +152,10 @@ class {{classname}}(Model): {{/hasValidation}} {{/isEnum}} - self._{{name}} = {{name}}{{^-last}} + self._{{name}} = {{name}}{{^@last}} -{{/-last}} +{{/@last}} {{/vars}} {{/model}} -{{/models}} \ No newline at end of file +{{/models}} diff --git a/src/main/resources/handlebars/pythonFlaskConnexion/swagger.mustache b/src/main/resources/handlebars/pythonFlaskConnexion/swagger.mustache index 51560926bb..34fbb53f33 100644 --- a/src/main/resources/handlebars/pythonFlaskConnexion/swagger.mustache +++ b/src/main/resources/handlebars/pythonFlaskConnexion/swagger.mustache @@ -1 +1 @@ -{{{swagger-yaml}}} \ No newline at end of file +{{{openapi-yaml}}} diff --git a/src/main/resources/mustache/pythonFlaskConnexion/swagger.mustache b/src/main/resources/mustache/pythonFlaskConnexion/swagger.mustache index 51560926bb..34fbb53f33 100644 --- a/src/main/resources/mustache/pythonFlaskConnexion/swagger.mustache +++ b/src/main/resources/mustache/pythonFlaskConnexion/swagger.mustache @@ -1 +1 @@ -{{{swagger-yaml}}} \ No newline at end of file +{{{openapi-yaml}}} From 3773336473c413acdca994eb172d2240303ccbc1 Mon Sep 17 00:00:00 2001 From: Hugo Mercado Date: Sun, 28 Apr 2019 17:58:45 -0500 Subject: [PATCH 05/11] changed template dir for python flask generator --- .../v3/generators/python/PythonFlaskConnexionCodegen.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/io/swagger/codegen/v3/generators/python/PythonFlaskConnexionCodegen.java b/src/main/java/io/swagger/codegen/v3/generators/python/PythonFlaskConnexionCodegen.java index 11698fb60a..361840c1a9 100644 --- a/src/main/java/io/swagger/codegen/v3/generators/python/PythonFlaskConnexionCodegen.java +++ b/src/main/java/io/swagger/codegen/v3/generators/python/PythonFlaskConnexionCodegen.java @@ -482,7 +482,7 @@ public String toModelName(String name) { @Override public String getDefaultTemplateDir() { - return "changeme"; + return "pythonFlaskConnexion"; } @Override From 692ed90a72a96a26438af81ba365b4a25410ad85 Mon Sep 17 00:00:00 2001 From: Hugo Mercado Date: Fri, 3 May 2019 04:29:08 -0500 Subject: [PATCH 06/11] updated router controller extension. --- .../python/PythonFlaskConnexionCodegen.java | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/src/main/java/io/swagger/codegen/v3/generators/python/PythonFlaskConnexionCodegen.java b/src/main/java/io/swagger/codegen/v3/generators/python/PythonFlaskConnexionCodegen.java index 361840c1a9..013c0acbbc 100644 --- a/src/main/java/io/swagger/codegen/v3/generators/python/PythonFlaskConnexionCodegen.java +++ b/src/main/java/io/swagger/codegen/v3/generators/python/PythonFlaskConnexionCodegen.java @@ -92,12 +92,6 @@ public PythonFlaskConnexionCodegen() { 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 @@ -146,6 +140,11 @@ public PythonFlaskConnexionCodegen() { public void processOpts() { super.processOpts(); //apiTemplateFiles.clear(); + /* + * 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 = getTemplateDir(); if (additionalProperties.containsKey(CodegenConstants.PACKAGE_NAME)) { setPackageName((String) additionalProperties.get(CodegenConstants.PACKAGE_NAME)); @@ -309,7 +308,7 @@ public String getSchemaType(Schema p) { @Override public void preprocessOpenAPI(OpenAPI openAPI) { - // need vendor extensions for x-swagger-router-controller + // need vendor extensions for x-openapi-router-controller Paths paths = openAPI.getPaths(); if(paths != null) { for(String pathname : paths.keySet()) { @@ -326,12 +325,13 @@ public void preprocessOpenAPI(OpenAPI openAPI) { if(operationId == null) { operationId = getOrGenerateOperationId(operation, pathname, method.toString()); } + operation.setOperationId(toOperationId(operationId)); - if(operation.getExtensions().get("x-swagger-router-controller") == null) { - operation.getExtensions().put( - "x-swagger-router-controller", - controllerPackage + "." + toApiFilename(tag) - ); + 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 || operation.getParameters().isEmpty()) { + continue; } for (Parameter param: operation.getParameters()) { // sanitize the param name but don't underscore it since it's used for request mapping @@ -386,10 +386,10 @@ private static List> sortOperationsByPath(List postProcessSupportingFileData(Map objs) { - OpenAPI swagger = (OpenAPI) objs.get("openapi"); - if(swagger != null) { + OpenAPI openAPI = (OpenAPI) objs.get("openAPI"); + if(openAPI != null) { try { - objs.put("openapi-yaml", Yaml.mapper().writeValueAsString(swagger)); + objs.put("openapi-yaml", Yaml.mapper().writeValueAsString(openAPI)); } catch (JsonProcessingException e) { LOGGER.error(e.getMessage(), e); } From bb74c9459698da7702ed886096d80126538fb32b Mon Sep 17 00:00:00 2001 From: Hugo Mercado Date: Fri, 3 May 2019 04:29:52 -0500 Subject: [PATCH 07/11] updated connexion version on requeriments, alsdo added generator on services files. --- .../META-INF/services/io.swagger.codegen.v3.CodegenConfig | 1 + .../resources/handlebars/pythonFlaskConnexion/model.mustache | 2 +- .../handlebars/pythonFlaskConnexion/requirements.mustache | 2 +- .../resources/mustache/pythonFlaskConnexion/model.mustache | 4 ++-- 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/main/resources/META-INF/services/io.swagger.codegen.v3.CodegenConfig b/src/main/resources/META-INF/services/io.swagger.codegen.v3.CodegenConfig index 770c44d6fd..ead2418945 100644 --- a/src/main/resources/META-INF/services/io.swagger.codegen.v3.CodegenConfig +++ b/src/main/resources/META-INF/services/io.swagger.codegen.v3.CodegenConfig @@ -22,6 +22,7 @@ io.swagger.codegen.v3.generators.kotlin.KotlinClientCodegen io.swagger.codegen.v3.generators.kotlin.KotlinServerCodegen io.swagger.codegen.v3.generators.php.PhpClientCodegen io.swagger.codegen.v3.generators.python.PythonClientCodegen +io.swagger.codegen.v3.generators.python.PythonFlaskConnexionCodegen io.swagger.codegen.v3.generators.scala.ScalaClientCodegen io.swagger.codegen.v3.generators.scala.AkkaHttpServerCodegen io.swagger.codegen.v3.generators.swift.Swift3Codegen diff --git a/src/main/resources/handlebars/pythonFlaskConnexion/model.mustache b/src/main/resources/handlebars/pythonFlaskConnexion/model.mustache index 2f08ab7c38..2a2a6ba71c 100644 --- a/src/main/resources/handlebars/pythonFlaskConnexion/model.mustache +++ b/src/main/resources/handlebars/pythonFlaskConnexion/model.mustache @@ -5,7 +5,7 @@ from datetime import date, datetime # noqa: F401 from typing import List, Dict # noqa: F401 -from {{modelPackage}}.base_model_ import Model +from {{packageName}}.{{modelPackage}}.base_model_ import Model {{#imports}}{{import}} # noqa: F401,E501 {{/imports}} from {{packageName}} import util diff --git a/src/main/resources/handlebars/pythonFlaskConnexion/requirements.mustache b/src/main/resources/handlebars/pythonFlaskConnexion/requirements.mustache index 04042d4b1d..82d82c1a49 100644 --- a/src/main/resources/handlebars/pythonFlaskConnexion/requirements.mustache +++ b/src/main/resources/handlebars/pythonFlaskConnexion/requirements.mustache @@ -1,4 +1,4 @@ -connexion == 1.1.15 +connexion == 2.2.0 python_dateutil == 2.6.0 {{#supportPython2}} typing == 3.5.2.2 diff --git a/src/main/resources/mustache/pythonFlaskConnexion/model.mustache b/src/main/resources/mustache/pythonFlaskConnexion/model.mustache index 7bb398f9f7..3f4c62fb1b 100644 --- a/src/main/resources/mustache/pythonFlaskConnexion/model.mustache +++ b/src/main/resources/mustache/pythonFlaskConnexion/model.mustache @@ -5,7 +5,7 @@ from datetime import date, datetime # noqa: F401 from typing import List, Dict # noqa: F401 -from {{modelPackage}}.base_model_ import Model +from {{packageName}}.{{modelPackage}}.base_model_ import Model {{#imports}}{{import}} # noqa: F401,E501 {{/imports}} from {{packageName}} import util @@ -158,4 +158,4 @@ class {{classname}}(Model): {{/vars}} {{/model}} -{{/models}} \ No newline at end of file +{{/models}} From d153caf4c0b5d5bd7a2629e3a18733fd04a4a47a Mon Sep 17 00:00:00 2001 From: Hugo Mercado Date: Sat, 4 May 2019 23:49:03 -0500 Subject: [PATCH 08/11] refactored flask generator class --- .../python/PythonFlaskConnexionCodegen.java | 133 ++++++++++++------ 1 file changed, 92 insertions(+), 41 deletions(-) diff --git a/src/main/java/io/swagger/codegen/v3/generators/python/PythonFlaskConnexionCodegen.java b/src/main/java/io/swagger/codegen/v3/generators/python/PythonFlaskConnexionCodegen.java index 013c0acbbc..ae4770e7ca 100644 --- a/src/main/java/io/swagger/codegen/v3/generators/python/PythonFlaskConnexionCodegen.java +++ b/src/main/java/io/swagger/codegen/v3/generators/python/PythonFlaskConnexionCodegen.java @@ -5,12 +5,23 @@ import com.google.common.collect.Lists; import com.google.common.collect.Multimap; -//import io.swagger.codegen.*; -import io.swagger.codegen.v3.*; +import io.swagger.codegen.v3.CliOption; +import io.swagger.codegen.v3.CodegenConstants; +import io.swagger.codegen.v3.CodegenModel; +import io.swagger.codegen.v3.CodegenOperation; +import io.swagger.codegen.v3.CodegenParameter; +import io.swagger.codegen.v3.CodegenProperty; +import io.swagger.codegen.v3.CodegenType; +import io.swagger.codegen.v3.SupportingFile; import io.swagger.codegen.v3.generators.DefaultCodegenConfig; import java.io.File; -import java.util.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; import io.swagger.v3.core.util.Yaml; import io.swagger.v3.oas.models.OpenAPI; @@ -19,6 +30,7 @@ import io.swagger.v3.oas.models.Paths; import io.swagger.v3.oas.models.media.*; import io.swagger.v3.oas.models.parameters.Parameter; +import io.swagger.v3.oas.models.security.SecurityScheme; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -56,6 +68,8 @@ public PythonFlaskConnexionCodegen() { languageSpecificPrimitives.add("date"); languageSpecificPrimitives.add("file"); languageSpecificPrimitives.add("object"); + languageSpecificPrimitives.add("byte"); + languageSpecificPrimitives.add("bytearray"); typeMapping.clear(); typeMapping.put("integer", "int"); @@ -72,6 +86,8 @@ public PythonFlaskConnexionCodegen() { 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( @@ -183,6 +199,7 @@ public void processOpts() { 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("swagger.mustache", packageName + File.separatorChar + "swagger", "swagger.yaml")); + supportingFiles.add(new SupportingFile("authorization_controller.mustache", packageName + File.separatorChar + controllerPackage, "authorization_controller.py")); modelPackage = packageName + "." + modelPackage; controllerPackage = packageName + "." + controllerPackage; @@ -308,44 +325,10 @@ public String getSchemaType(Schema p) { @Override public void preprocessOpenAPI(OpenAPI openAPI) { - // need vendor extensions for x-openapi-router-controller - Paths paths = openAPI.getPaths(); - if(paths != null) { - for(String pathname : paths.keySet()) { - PathItem path = paths.get(pathname); - Map operationMap = path.readOperationsMap(); - if(operationMap != null) { - for(PathItem.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 || operation.getParameters().isEmpty()) { - continue; - } - for (Parameter param: operation.getParameters()) { - // sanitize the param name but don't underscore it since it's used for request mapping - String name = param.getName(); - String paramName = sanitizeName(name); - if (!paramName.equals(name)) { - LOGGER.warn(name + " cannot be used as parameter name with flask-connexion and was sanitized as " + paramName); - } - param.setName(paramName); - } - } - } - } - } + final Paths paths = openAPI.getPaths(); + addRouterControllerExtensions(paths); + final Map securitySchemes = openAPI.getComponents() != null ? openAPI.getComponents().getSecuritySchemes() : null; + addSecurityExtensions(securitySchemes); } @SuppressWarnings("unchecked") @@ -711,4 +694,72 @@ public void postProcessPattern(String pattern, Map vendorExtensi vendorExtensions.put("x-modifiers", modifiers); } } + + protected void addRouterControllerExtensions(Paths paths) { + if(paths == null || paths.isEmpty()) { + return; + } + // need vendor extensions for x-openapi-router-controller + for(String pathname : paths.keySet()) { + final PathItem path = paths.get(pathname); + final Map operationMap = path.readOperationsMap(); + + if(operationMap == null || operationMap.isEmpty()) { + continue; + } + for(PathItem.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 || operation.getParameters().isEmpty()) { + continue; + } + for (Parameter param: operation.getParameters()) { + // sanitize the param name but don't underscore it since it's used for request mapping + String name = param.getName(); + String paramName = sanitizeName(name); + if (!paramName.equals(name)) { + LOGGER.warn(name + " cannot be used as parameter name with flask-connexion and was sanitized as " + paramName); + } + param.setName(paramName); + } + } + } + } + + protected void addSecurityExtensions(Map securitySchemes) { + if (securitySchemes == null || securitySchemes.isEmpty()) { + return; + } + for (String securityName : securitySchemes.keySet()) { + final SecurityScheme securityScheme = securitySchemes.get(securityName); + final String functionName = controllerPackage + ".authorization_controller.check_" + securityName; + + if (SecurityScheme.Type.OAUTH2.equals(securityScheme.getType())) { + securityScheme.addExtension("x-tokenInfoFunc", functionName); + securityScheme.addExtension("x-scopeValidateFunc", controllerPackage + ".authorization_controller.validate_scope_" + securityName); + } else if (SecurityScheme.Type.HTTP.equals(securityScheme.getType())) { + if ("basic".equals(securityScheme.getScheme())) { + securityScheme.addExtension("x-basicInfoFunc", functionName); + } else if ("bearer".equals(securityScheme.getScheme())) { + securityScheme.addExtension("x-bearerInfoFunc", functionName); + } + } else if (SecurityScheme.Type.APIKEY.equals(securityScheme.getType())) { + securityScheme.addExtension("x-apikeyInfoFunc", functionName); + } else { + LOGGER.warn("Security type " + securityScheme.getType().toString() + " is not supported."); + } + } + } } From 228b9de9229541d7b42e77e1f6c03b3ac6abbe26 Mon Sep 17 00:00:00 2001 From: Hugo Mercado Date: Sat, 4 May 2019 23:49:26 -0500 Subject: [PATCH 09/11] added authorization controller template --- .../authorization_controller.mustache | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 src/main/resources/handlebars/pythonFlaskConnexion/authorization_controller.mustache diff --git a/src/main/resources/handlebars/pythonFlaskConnexion/authorization_controller.mustache b/src/main/resources/handlebars/pythonFlaskConnexion/authorization_controller.mustache new file mode 100644 index 0000000000..723357e74b --- /dev/null +++ b/src/main/resources/handlebars/pythonFlaskConnexion/authorization_controller.mustache @@ -0,0 +1,31 @@ +from typing import List +""" +controller generated to handled auth operation described at: +https://connexion.readthedocs.io/en/latest/security.html +""" +{{#authMethods}} +{{#isApiKey}} +def check_{{name}}(api_key, required_scopes): + return {'test_key': 'test_value'} + +{{/isApiKey}} +{{#isBasic}} +def check_{{name}}(username, password, required_scopes): + return {'test_key': 'test_value'} + +{{/isBasic}} +{{#isBearer}} +def check_{{name}}(token): + return {'test_key': 'test_value'} + +{{/isBearer}} +{{#isOAuth}} +def check_{{name}}(token): + return {'scopes': ['read:pets', 'write:pets'], 'uid': 'test_value'} + +def validate_scope_{{name}}(required_scopes, token_scopes): + return set(required_scopes).issubset(set(token_scopes)) + +{{/isOAuth}} +{{/authMethods}} + From c8708e58715b95dd1aaf6db78d95d8df07ab31c7 Mon Sep 17 00:00:00 2001 From: Hugo Mercado Date: Sat, 11 May 2019 00:56:02 -0500 Subject: [PATCH 10/11] map bigdecimal to float for flask generator. Also updated mustache templates. --- .../python/PythonFlaskConnexionCodegen.java | 2 ++ .../authorization_controller.mustache | 31 +++++++++++++++++++ .../requirements.mustache | 2 +- 3 files changed, 34 insertions(+), 1 deletion(-) create mode 100644 src/main/resources/mustache/pythonFlaskConnexion/authorization_controller.mustache diff --git a/src/main/java/io/swagger/codegen/v3/generators/python/PythonFlaskConnexionCodegen.java b/src/main/java/io/swagger/codegen/v3/generators/python/PythonFlaskConnexionCodegen.java index ae4770e7ca..84a151c4ef 100644 --- a/src/main/java/io/swagger/codegen/v3/generators/python/PythonFlaskConnexionCodegen.java +++ b/src/main/java/io/swagger/codegen/v3/generators/python/PythonFlaskConnexionCodegen.java @@ -75,6 +75,7 @@ public PythonFlaskConnexionCodegen() { typeMapping.put("integer", "int"); typeMapping.put("float", "float"); typeMapping.put("number", "float"); + typeMapping.put("BigDecimal", "float"); typeMapping.put("long", "int"); typeMapping.put("double", "float"); typeMapping.put("array", "List"); @@ -86,6 +87,7 @@ public PythonFlaskConnexionCodegen() { typeMapping.put("object", "object"); typeMapping.put("file", "file"); typeMapping.put("UUID", "str"); + typeMapping.put("binary", "str"); typeMapping.put("byte", "bytearray"); typeMapping.put("ByteArray", "bytearray"); diff --git a/src/main/resources/mustache/pythonFlaskConnexion/authorization_controller.mustache b/src/main/resources/mustache/pythonFlaskConnexion/authorization_controller.mustache new file mode 100644 index 0000000000..723357e74b --- /dev/null +++ b/src/main/resources/mustache/pythonFlaskConnexion/authorization_controller.mustache @@ -0,0 +1,31 @@ +from typing import List +""" +controller generated to handled auth operation described at: +https://connexion.readthedocs.io/en/latest/security.html +""" +{{#authMethods}} +{{#isApiKey}} +def check_{{name}}(api_key, required_scopes): + return {'test_key': 'test_value'} + +{{/isApiKey}} +{{#isBasic}} +def check_{{name}}(username, password, required_scopes): + return {'test_key': 'test_value'} + +{{/isBasic}} +{{#isBearer}} +def check_{{name}}(token): + return {'test_key': 'test_value'} + +{{/isBearer}} +{{#isOAuth}} +def check_{{name}}(token): + return {'scopes': ['read:pets', 'write:pets'], 'uid': 'test_value'} + +def validate_scope_{{name}}(required_scopes, token_scopes): + return set(required_scopes).issubset(set(token_scopes)) + +{{/isOAuth}} +{{/authMethods}} + diff --git a/src/main/resources/mustache/pythonFlaskConnexion/requirements.mustache b/src/main/resources/mustache/pythonFlaskConnexion/requirements.mustache index 04042d4b1d..82d82c1a49 100644 --- a/src/main/resources/mustache/pythonFlaskConnexion/requirements.mustache +++ b/src/main/resources/mustache/pythonFlaskConnexion/requirements.mustache @@ -1,4 +1,4 @@ -connexion == 1.1.15 +connexion == 2.2.0 python_dateutil == 2.6.0 {{#supportPython2}} typing == 3.5.2.2 From fa6e79d57a1cbd2760bc6bb9f699aa0ceda1e5e2 Mon Sep 17 00:00:00 2001 From: Hugo Mercado Date: Tue, 21 May 2019 19:09:18 -0500 Subject: [PATCH 11/11] convert parameter to snake_case. --- .../v3/generators/python/PythonFlaskConnexionCodegen.java | 4 ++-- .../handlebars/pythonFlaskConnexion/__main__.mustache | 2 +- .../resources/mustache/pythonFlaskConnexion/__main__.mustache | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/java/io/swagger/codegen/v3/generators/python/PythonFlaskConnexionCodegen.java b/src/main/java/io/swagger/codegen/v3/generators/python/PythonFlaskConnexionCodegen.java index 84a151c4ef..db2188a952 100644 --- a/src/main/java/io/swagger/codegen/v3/generators/python/PythonFlaskConnexionCodegen.java +++ b/src/main/java/io/swagger/codegen/v3/generators/python/PythonFlaskConnexionCodegen.java @@ -421,10 +421,10 @@ public String toVarName(String name) { public String toParamName(String name) { // don't do name =removeNonNameElementToCamelCase(name); // this breaks connexion, which does not modify param names before sending them if (reservedWords.contains(name)) { - return escapeReservedWord(name); + name = escapeReservedWord(name); } // Param name is already sanitized in swagger spec processing - return name; + return toVarName(name); } @Override diff --git a/src/main/resources/handlebars/pythonFlaskConnexion/__main__.mustache b/src/main/resources/handlebars/pythonFlaskConnexion/__main__.mustache index 1075634722..1da357d70f 100644 --- a/src/main/resources/handlebars/pythonFlaskConnexion/__main__.mustache +++ b/src/main/resources/handlebars/pythonFlaskConnexion/__main__.mustache @@ -13,7 +13,7 @@ from {{packageName}} import encoder def main(): app = connexion.App(__name__, specification_dir='./swagger/') app.app.json_encoder = encoder.JSONEncoder - app.add_api('swagger.yaml', arguments={'title': '{{appName}}'}) + app.add_api('swagger.yaml', arguments={'title': '{{appName}}'}, pythonic_params=True) app.run(port={{serverPort}}) diff --git a/src/main/resources/mustache/pythonFlaskConnexion/__main__.mustache b/src/main/resources/mustache/pythonFlaskConnexion/__main__.mustache index 1075634722..1da357d70f 100644 --- a/src/main/resources/mustache/pythonFlaskConnexion/__main__.mustache +++ b/src/main/resources/mustache/pythonFlaskConnexion/__main__.mustache @@ -13,7 +13,7 @@ from {{packageName}} import encoder def main(): app = connexion.App(__name__, specification_dir='./swagger/') app.app.json_encoder = encoder.JSONEncoder - app.add_api('swagger.yaml', arguments={'title': '{{appName}}'}) + app.add_api('swagger.yaml', arguments={'title': '{{appName}}'}, pythonic_params=True) app.run(port={{serverPort}})