diff --git a/src/main/java/io/swagger/codegen/v3/generators/python/PythonClientCodegen.java b/src/main/java/io/swagger/codegen/v3/generators/python/PythonClientCodegen.java new file mode 100644 index 0000000000..1898d295d0 --- /dev/null +++ b/src/main/java/io/swagger/codegen/v3/generators/python/PythonClientCodegen.java @@ -0,0 +1,691 @@ +package io.swagger.codegen.v3.generators.python; + +import io.swagger.codegen.v3.CliOption; +import io.swagger.codegen.v3.CodegenConstants; +import io.swagger.codegen.v3.CodegenModel; +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 io.swagger.v3.oas.models.media.ArraySchema; +import io.swagger.v3.oas.models.media.BooleanSchema; +import io.swagger.v3.oas.models.media.DateSchema; +import io.swagger.v3.oas.models.media.DateTimeSchema; +import io.swagger.v3.oas.models.media.IntegerSchema; +import io.swagger.v3.oas.models.media.MapSchema; +import io.swagger.v3.oas.models.media.NumberSchema; +import io.swagger.v3.oas.models.media.Schema; +import io.swagger.v3.oas.models.media.StringSchema; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.regex.Pattern; + +import static io.swagger.codegen.v3.generators.handlebars.ExtensionHelper.getBooleanValue; + +public class PythonClientCodegen extends DefaultCodegenConfig { + + private static final Logger LOGGER = LoggerFactory.getLogger(PythonClientCodegen.class); + + public static final String PACKAGE_URL = "packageUrl"; + public static final String DEFAULT_LIBRARY = "urllib3"; + + protected String packageName; // e.g. petstore_api + protected String packageVersion; + protected String projectName; // for setup.py, e.g. petstore-api + protected String packageUrl; + protected String apiDocPath = "docs/"; + protected String modelDocPath = "docs/"; + + protected Map regexModifiers; + + private String testFolder; + + public PythonClientCodegen() { + super(); + + // clear import mapping (from default generator) as python does not use it + // at the moment + importMapping.clear(); + + supportsInheritance = true; + modelPackage = "models"; + apiPackage = "api"; + outputFolder = "generated-code" + File.separatorChar + "python"; + + modelTemplateFiles.put("model.mustache", ".py"); + apiTemplateFiles.put("api.mustache", ".py"); + + modelTestTemplateFiles.put("model_test.mustache", ".py"); + apiTestTemplateFiles.put("api_test.mustache", ".py"); + + modelDocTemplateFiles.put("model_doc.mustache", ".md"); + apiDocTemplateFiles.put("api_doc.mustache", ".md"); + + testFolder = "test"; + + // default HIDE_GENERATION_TIMESTAMP to true + hideGenerationTimestamp = Boolean.TRUE; + + 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("object"); + + instantiationTypes.put("map", "dict"); + + 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"); + // TODO binary should be mapped to byte array + // mapped to String as a workaround + typeMapping.put("binary", "str"); + typeMapping.put("ByteArray", "str"); + // map uuid to string for the time being + typeMapping.put("UUID", "str"); + + // from https://docs.python.org/3/reference/lexical_analysis.html#keywords + setReservedWordsLowerCase( + Arrays.asList( + // local variable name used in API methods (endpoints) + "all_params", "resource_path", "path_params", "query_params", + "header_params", "form_params", "local_var_files", "body_params", "auth_settings", + // @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", "nonlocal", "None", "True", "nonlocal", + "float", "int", "str", "date", "datetime")); + + 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.clear(); + cliOptions.add(new CliOption(CodegenConstants.PACKAGE_NAME, "python package name (convention: snake_case).") + .defaultValue("swagger_client")); + cliOptions.add(new CliOption(CodegenConstants.PROJECT_NAME, "python project name in setup.py (e.g. petstore-api).")); + cliOptions.add(new CliOption(CodegenConstants.PACKAGE_VERSION, "python package version.") + .defaultValue("1.0.0")); + cliOptions.add(new CliOption(PACKAGE_URL, "python package URL.")); + cliOptions.add(CliOption.newBoolean(CodegenConstants.SORT_PARAMS_BY_REQUIRED_FLAG, + CodegenConstants.SORT_PARAMS_BY_REQUIRED_FLAG_DESC).defaultValue(Boolean.TRUE.toString())); + cliOptions.add(new CliOption(CodegenConstants.HIDE_GENERATION_TIMESTAMP, CodegenConstants.HIDE_GENERATION_TIMESTAMP_DESC) + .defaultValue(Boolean.TRUE.toString())); + + supportedLibraries.put("urllib3", "urllib3-based client"); + supportedLibraries.put("asyncio", "Asyncio-based client (python 3.5+)"); + supportedLibraries.put("tornado", "tornado-based client"); + CliOption libraryOption = new CliOption(CodegenConstants.LIBRARY, "library template (sub-template) to use"); + libraryOption.setDefault(DEFAULT_LIBRARY); + cliOptions.add(libraryOption); + setLibrary(DEFAULT_LIBRARY); + } + + @Override + public void processOpts() { + super.processOpts(); + Boolean excludeTests = false; + + embeddedTemplateDir = templateDir = getTemplateDir(); + + if(additionalProperties.containsKey(CodegenConstants.EXCLUDE_TESTS)) { + excludeTests = Boolean.valueOf(additionalProperties.get(CodegenConstants.EXCLUDE_TESTS).toString()); + } + + if (additionalProperties.containsKey(CodegenConstants.PACKAGE_NAME)) { + setPackageName((String) additionalProperties.get(CodegenConstants.PACKAGE_NAME)); + } + else { + setPackageName("swagger_client"); + } + + if (additionalProperties.containsKey(CodegenConstants.PROJECT_NAME)) { + setProjectName((String) additionalProperties.get(CodegenConstants.PROJECT_NAME)); + } + else { + // default: set project based on package name + // e.g. petstore_api (package name) => petstore-api (project name) + setProjectName(packageName.replaceAll("_", "-")); + } + + if (additionalProperties.containsKey(CodegenConstants.PACKAGE_VERSION)) { + setPackageVersion((String) additionalProperties.get(CodegenConstants.PACKAGE_VERSION)); + } + else { + setPackageVersion("1.0.0"); + } + + additionalProperties.put(CodegenConstants.PROJECT_NAME, projectName); + additionalProperties.put(CodegenConstants.PACKAGE_NAME, packageName); + additionalProperties.put(CodegenConstants.PACKAGE_VERSION, packageVersion); + + // make api and model doc path available in mustache template + additionalProperties.put("apiDocPath", apiDocPath); + additionalProperties.put("modelDocPath", modelDocPath); + + if (additionalProperties.containsKey(PACKAGE_URL)) { + setPackageUrl((String) additionalProperties.get(PACKAGE_URL)); + } + + supportingFiles.add(new SupportingFile("README.mustache", "", "README.md")); + + 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("configuration.mustache", packageName, "configuration.py")); + supportingFiles.add(new SupportingFile("__init__package.mustache", packageName, "__init__.py")); + supportingFiles.add(new SupportingFile("__init__model.mustache", packageName + File.separatorChar + modelPackage, "__init__.py")); + supportingFiles.add(new SupportingFile("__init__api.mustache", packageName + File.separatorChar + apiPackage, "__init__.py")); + + if(Boolean.FALSE.equals(excludeTests)) { + supportingFiles.add(new SupportingFile("__init__test.mustache", testFolder, "__init__.py")); + } + 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("setup.mustache", "", "setup.py")); + supportingFiles.add(new SupportingFile("api_client.mustache", packageName, "api_client.py")); + + if ("asyncio".equals(getLibrary())) { + supportingFiles.add(new SupportingFile("asyncio/rest.mustache", packageName, "rest.py")); + additionalProperties.put("asyncio", "true"); + } else if ("tornado".equals(getLibrary())) { + supportingFiles.add(new SupportingFile("tornado/rest.mustache", packageName, "rest.py")); + additionalProperties.put("tornado", "true"); + } else { + supportingFiles.add(new SupportingFile("rest.mustache", packageName, "rest.py")); + } + + modelPackage = packageName + "." + modelPackage; + apiPackage = packageName + "." + apiPackage; + + } + + private static String dropDots(String str) { + return str.replaceAll("\\.", "_"); + } + + @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 Map postProcessModels(Map objs) { + // process enum in models + return postProcessModelsEnum(objs); + } + + @Override + public void postProcessParameter(CodegenParameter parameter){ + postProcessPattern(parameter.pattern, parameter.vendorExtensions); + } + + @Override + public void postProcessModelProperty(CodegenModel model, CodegenProperty property) { + postProcessPattern(property.pattern, property.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) { + throw new IllegalArgumentException("Pattern must follow the Perl " + + "/pattern/modifiers convention. "+pattern+" is not valid."); + } + + 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); + } + } + + @Override + public CodegenType getTag() { + return CodegenType.CLIENT; + } + + @Override + public String getName() { + return "python"; + } + + @Override + public String getHelp() { + return "Generates a Python client library."; + } + + @Override + public String escapeReservedWord(String name) { + if(this.reservedWordsMappings().containsKey(name)) { + return this.reservedWordsMappings().get(name); + } + return "_" + name; + } + + @Override + public String apiDocFileFolder() { + return (outputFolder + "/" + apiDocPath); + } + + @Override + public String modelDocFileFolder() { + return (outputFolder + "/" + modelDocPath); + } + + @Override + public String toModelDocFilename(String name) { + return toModelName(name); + } + + @Override + public String toApiDocFilename(String name) { + return toApiName(name); + } + + + @Override + public String apiFileFolder() { + return outputFolder + File.separatorChar + apiPackage().replace('.', File.separatorChar); + } + + @Override + public String modelFileFolder() { + return outputFolder + File.separatorChar + modelPackage().replace('.', File.separatorChar); + } + + @Override + public String apiTestFileFolder() { + return outputFolder + File.separatorChar + testFolder; + } + + @Override + public String modelTestFileFolder() { + return outputFolder + File.separatorChar + testFolder; + } + + @Override + public String toInstantiationType(Schema schema) { + if (schema instanceof MapSchema) { + return instantiationTypes.get("map"); + } + return null; + } + + @Override + public String getTypeDeclaration(Schema schema) { + if (schema instanceof ArraySchema) { + ArraySchema ap = (ArraySchema) schema; + Schema inner = ap.getItems(); + return getSchemaType(schema) + "[" + getTypeDeclaration(inner) + "]"; + } else if (schema instanceof MapSchema) { + MapSchema mapSchema = (MapSchema) schema; + Schema inner = (Schema) mapSchema.getAdditionalProperties(); + + return getSchemaType(schema) + "(str, " + getTypeDeclaration(inner) + ")"; + } + return super.getTypeDeclaration(schema); + } + + @Override + public String getSchemaType(Schema schema) { + String swaggerType = super.getSchemaType(schema); + 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 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) { + // to avoid conflicts with 'callback' parameter for async call + if ("callback".equals(name)) { + return "param_callback"; + } + + // should be the same as variable name + return toVarName(name); + } + + @Override + public String 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 toModelFilename(String name) { + // underscore the model file name + // PhoneNumber => phone_number + return underscore(dropDots(toModelName(name))); + } + + @Override + public String toModelTestFilename(String name) { + return "test_" + toModelFilename(name); + } + + @Override + public String toApiFilename(String name) { + // replace - with _ e.g. created-at => created_at + name = name.replaceAll("-", "_"); + + // e.g. PhoneNumberApi.py => phone_number_api.py + return underscore(name) + "_api"; + } + + @Override + public String toApiTestFilename(String name) { + return "test_" + toApiFilename(name); + } + + @Override + public String toApiName(String name) { + if (name.length() == 0) { + return "DefaultApi"; + } + // e.g. phone_number_api => PhoneNumberApi + return camelize(name) + "Api"; + } + + @Override + public String toApiVarName(String name) { + if (name.length() == 0) { + return "default_api"; + } + return underscore(name) + "_api"; + } + + @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)); + } + + public void setPackageName(String packageName) { + this.packageName = packageName; + } + + public void setProjectName(String projectName) { + this.projectName= projectName; + } + + public void setPackageVersion(String packageVersion) { + this.packageVersion = packageVersion; + } + + public void setPackageUrl(String packageUrl) { + this.packageUrl = packageUrl; + } + + /** + * Generate Python package name from String `packageName` + * + * (PEP 0008) Python packages should also have short, all-lowercase names, + * although the use of underscores is discouraged. + * + * @param packageName Package name + * @return Python package name that conforms to PEP 0008 + */ + @SuppressWarnings("static-method") + public String generatePackageName(String packageName) { + return underscore(packageName.replaceAll("[^\\w]+", "")); + } + + /** + * Return the default value of the property + * + * @param propertySchema Swagger property object + * @return string presentation of the default value of the property + */ + @Override + public String toDefaultValue(Schema propertySchema) { + if (propertySchema instanceof StringSchema) { + StringSchema stringSchema = (StringSchema) propertySchema; + if (stringSchema.getDefault() != null) { + if (Pattern.compile("\r\n|\r|\n").matcher(stringSchema.getDefault()).find()) + return "'''" + stringSchema.getDefault() + "'''"; + else + return "'" + stringSchema.getDefault() + "'"; + } + } else if (propertySchema instanceof BooleanSchema) { + BooleanSchema booleanSchema = (BooleanSchema) propertySchema; + if (booleanSchema.getDefault() != null) { + if (booleanSchema.getDefault().toString().equalsIgnoreCase("false")) + return "False"; + else + return "True"; + } + } else if (propertySchema instanceof DateSchema) { + // TODO + } else if (propertySchema instanceof DateTimeSchema) { + // TODO + } else if (propertySchema instanceof NumberSchema) { + NumberSchema numberSchema = (NumberSchema) propertySchema; + if (numberSchema.getDefault() != null) { + return numberSchema.getDefault().toString(); + } + } else if (propertySchema instanceof IntegerSchema) { + IntegerSchema integerSchema = (IntegerSchema) propertySchema; + if (integerSchema.getDefault() != null) { + return integerSchema.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 (example == null) { + example = "56"; + } + } else if ("Float".equalsIgnoreCase(type) || "Double".equalsIgnoreCase(type)) { + if (example == null) { + example = "3.4"; + } + } else if ("BOOLEAN".equalsIgnoreCase(type) || "bool".equalsIgnoreCase(type)) { + if (example == null) { + example = "True"; + } + } else if ("file".equalsIgnoreCase(type)) { + if (example == null) { + example = "/path/to/file"; + } + example = "'" + escapeText(example) + "'"; + } 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 = this.packageName + "." + type + "()"; + } else { + LOGGER.warn("Type " + type + " not handled properly in setParameterExampleValue"); + } + + if (example == null) { + example = "NULL"; + } else if (getBooleanValue(p, CodegenConstants.IS_LIST_CONTAINER_EXT_NAME)) { + example = "[" + example + "]"; + } else if (getBooleanValue(p, CodegenConstants.IS_MAP_CONTAINER_EXT_NAME)) { + example = "{'key': " + example + "}"; + } + + p.example = example; + } + + @Override + public String sanitizeTag(String tag) { + return sanitizeName(tag); + } + + @Override + public String getDefaultTemplateDir() { + return "python"; + } + + @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("'''", "'_'_'"); + } +} 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 37a82d3261..770c44d6fd 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 @@ -21,6 +21,7 @@ io.swagger.codegen.v3.generators.openapi.OpenAPIYamlGenerator 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.scala.ScalaClientCodegen io.swagger.codegen.v3.generators.scala.AkkaHttpServerCodegen io.swagger.codegen.v3.generators.swift.Swift3Codegen diff --git a/src/main/resources/handlebars/python/README.mustache b/src/main/resources/handlebars/python/README.mustache new file mode 100644 index 0000000000..56e999144e --- /dev/null +++ b/src/main/resources/handlebars/python/README.mustache @@ -0,0 +1,129 @@ +# {{{projectName}}} +{{#appDescription}} +{{{appDescription}}} +{{/appDescription}} + +This Python package is automatically generated by the [Swagger Codegen](https://github.com/swagger-api/swagger-codegen) project: + +- API version: {{appVersion}} +- Package version: {{packageVersion}} +{{^hideGenerationTimestamp}} +- Build date: {{generatedDate}} +{{/hideGenerationTimestamp}} +- Build package: {{generatorClass}} +{{#infoUrl}} +For more information, please visit [{{{infoUrl}}}]({{{infoUrl}}}) +{{/infoUrl}} + +## Requirements. + +Python 2.7 and 3.4+ + +## Installation & Usage +### pip install + +If the python package is hosted on Github, you can install directly from Github + +```sh +pip install git+https://github.com/{{{gitUserId}}}/{{{gitRepoId}}}.git +``` +(you may need to run `pip` with root permission: `sudo pip install git+https://github.com/{{{gitUserId}}}/{{{gitRepoId}}}.git`) + +Then import the package: +```python +import {{{packageName}}} +``` + +### Setuptools + +Install via [Setuptools](http://pypi.python.org/pypi/setuptools). + +```sh +python setup.py install --user +``` +(or `sudo python setup.py install` to install the package for all users) + +Then import the package: +```python +import {{{packageName}}} +``` + +## Getting Started + +Please follow the [installation procedure](#installation--usage) and then run the following: + +```python +from __future__ import print_function +import time +import {{{packageName}}} +from {{{packageName}}}.rest import ApiException +from pprint import pprint +{{#apiInfo}}{{#apis}}{{#@first}}{{#operations}}{{#operation}}{{#contents}}{{#@first}}{{#hasAuthMethods}}{{#authMethods}}{{#isBasic}} +# Configure HTTP basic authorization: {{{name}}} +configuration = {{{packageName}}}.Configuration() +configuration.username = 'YOUR_USERNAME' +configuration.password = 'YOUR_PASSWORD'{{/isBasic}}{{#isApiKey}} +# Configure API key authorization: {{{name}}} +configuration = {{{packageName}}}.Configuration() +configuration.api_key['{{{keyParamName}}}'] = 'YOUR_API_KEY' +# Uncomment below to setup prefix (e.g. Bearer) for API key, if needed +# configuration.api_key_prefix['{{{keyParamName}}}'] = 'Bearer'{{/isApiKey}}{{#isOAuth}} +# Configure OAuth2 access token for authorization: {{{name}}} +configuration = {{{packageName}}}.Configuration() +configuration.access_token = 'YOUR_ACCESS_TOKEN'{{/isOAuth}}{{/authMethods}} +{{/hasAuthMethods}} + +# create an instance of the API class +api_instance = {{{packageName}}}.{{{classname}}}({{{packageName}}}.ApiClient(configuration)) +{{#parameters}}{{paramName}} = {{{example}}} # {{{dataType}}} | {{{description}}}{{^required}} (optional){{/required}}{{#defaultValue}} (default to {{{.}}}){{/defaultValue}} +{{/parameters}} + +try: +{{#summary}} # {{{.}}} +{{/summary}} {{#returnType}}api_response = {{/returnType}}api_instance.{{{operationId}}}({{#parameters}}{{#required}}{{paramName}}{{/required}}{{^required}}{{paramName}}={{paramName}}{{/required}}{{#hasMore}}, {{/hasMore}}{{/parameters}}){{#returnType}} + pprint(api_response){{/returnType}} +except ApiException as e: + print("Exception when calling {{classname}}->{{operationId}}: %s\n" % e) +{{/@first}}{{/contents}}{{/operation}}{{/operations}}{{/@first}}{{/apis}}{{/apiInfo}} +``` + +## Documentation for API Endpoints + +All URIs are relative to *{{basePath}}* + +Class | Method | HTTP request | Description +------------ | ------------- | ------------- | ------------- +{{#apiInfo}}{{#apis}}{{#operations}}{{#operation}}*{{classname}}* | [**{{operationId}}**]({{apiDocPath}}{{classname}}.md#{{operationIdLowerCase}}) | **{{httpMethod}}** {{path}} | {{#summary}}{{summary}}{{/summary}} +{{/operation}}{{/operations}}{{/apis}}{{/apiInfo}} + +## Documentation For Models + +{{#models}}{{#model}} - [{{{classname}}}]({{modelDocPath}}{{{classname}}}.md) +{{/model}}{{/models}} + +## Documentation For Authorization + +{{^authMethods}} All endpoints do not require authorization. +{{/authMethods}}{{#authMethods}}{{#last}} Authentication schemes defined for the API:{{/last}}{{/authMethods}} +{{#authMethods}}## {{{name}}} + +{{#isApiKey}}- **Type**: API key +- **API key parameter name**: {{{keyParamName}}} +- **Location**: {{#isKeyInQuery}}URL query string{{/isKeyInQuery}}{{#isKeyInHeader}}HTTP header{{/isKeyInHeader}} +{{/isApiKey}} +{{#isBasic}}- **Type**: HTTP basic authentication +{{/isBasic}} +{{#isOAuth}}- **Type**: OAuth +- **Flow**: {{{flow}}} +- **Authorization URL**: {{{authorizationUrl}}} +- **Scopes**: {{^scopes}}N/A{{/scopes}} +{{#scopes}} - **{{{scope}}}**: {{{description}}} +{{/scopes}} +{{/isOAuth}} + +{{/authMethods}} + +## Author + +{{#apiInfo}}{{#apis}}{{^hasMore}}{{infoEmail}} +{{/hasMore}}{{/apis}}{{/apiInfo}} diff --git a/src/main/resources/handlebars/python/__init__api.mustache b/src/main/resources/handlebars/python/__init__api.mustache new file mode 100644 index 0000000000..db658a10fa --- /dev/null +++ b/src/main/resources/handlebars/python/__init__api.mustache @@ -0,0 +1,7 @@ +from __future__ import absolute_import + +# flake8: noqa + +# import apis into api package +{{#apiInfo}}{{#apis}}from {{apiPackage}}.{{classVarName}} import {{classname}} +{{/apis}}{{/apiInfo}} \ No newline at end of file diff --git a/src/main/resources/handlebars/python/__init__model.mustache b/src/main/resources/handlebars/python/__init__model.mustache new file mode 100644 index 0000000000..2266b3d17f --- /dev/null +++ b/src/main/resources/handlebars/python/__init__model.mustache @@ -0,0 +1,10 @@ +# coding: utf-8 + +# flake8: noqa +{{>partial_header}} + +from __future__ import absolute_import + +# import models into model package +{{#models}}{{#model}}from {{modelPackage}}.{{classFilename}} import {{classname}}{{/model}} +{{/models}} diff --git a/src/main/resources/handlebars/python/__init__package.mustache b/src/main/resources/handlebars/python/__init__package.mustache new file mode 100644 index 0000000000..cd42506f54 --- /dev/null +++ b/src/main/resources/handlebars/python/__init__package.mustache @@ -0,0 +1,17 @@ +# coding: utf-8 + +# flake8: noqa + +{{>partial_header}} + +from __future__ import absolute_import + +# import apis into sdk package +{{#apiInfo}}{{#apis}}from {{apiPackage}}.{{classVarName}} import {{classname}} +{{/apis}}{{/apiInfo}} +# import ApiClient +from {{packageName}}.api_client import ApiClient +from {{packageName}}.configuration import Configuration +# import models into sdk package +{{#models}}{{#model}}from {{modelPackage}}.{{classFilename}} import {{classname}} +{{/model}}{{/models}} \ No newline at end of file diff --git a/src/main/resources/handlebars/python/__init__test.mustache b/src/main/resources/handlebars/python/__init__test.mustache new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/main/resources/handlebars/python/api.mustache b/src/main/resources/handlebars/python/api.mustache new file mode 100644 index 0000000000..e0ec79a8f2 --- /dev/null +++ b/src/main/resources/handlebars/python/api.mustache @@ -0,0 +1,216 @@ +# coding: utf-8 + +{{>partial_header}} + +from __future__ import absolute_import + +import re # noqa: F401 + +# python 2 and python 3 compatibility library +import six + +from {{packageName}}.api_client import ApiClient + + +{{#operations}} +class {{classname}}(object): + """NOTE: This class is auto generated by the swagger code generator program. + + Do not edit the class manually. + Ref: https://github.com/swagger-api/swagger-codegen + """ + + def __init__(self, api_client=None): + if api_client is None: + api_client = ApiClient() + self.api_client = api_client +{{#operation}} +{{#contents}} + + def {{operationId}}(self, {{#sortParamsByRequiredFlag}}{{#parameters}}{{#required}}{{paramName}}, {{/required}}{{/parameters}}{{/sortParamsByRequiredFlag}}**kwargs): # noqa: E501 + """{{#summary}}{{{.}}}{{/summary}}{{^summary}}{{operationId}}{{/summary}} # noqa: E501 + +{{#notes}} + {{{notes}}} # noqa: E501 +{{/notes}} + This method makes a synchronous HTTP request by default. To make an + asynchronous HTTP request, please pass async_req=True +{{#sortParamsByRequiredFlag}} + >>> thread = api.{{operationId}}({{#parameters}}{{#required}}{{paramName}}, {{/required}}{{/parameters}}async_req=True) +{{/sortParamsByRequiredFlag}} +{{^sortParamsByRequiredFlag}} + >>> thread = api.{{operationId}}({{#parameters}}{{#required}}{{paramName}}={{paramName}}_value, {{/required}}{{/parameters}}async_req=True) +{{/sortParamsByRequiredFlag}} + >>> result = thread.get() + + :param async_req bool +{{#parameters}} + :param {{dataType}} {{paramName}}:{{#description}} {{{description}}}{{/description}}{{#required}} (required){{/required}}{{#optional}}(optional){{/optional}} +{{/parameters}} + :return: {{#returnType}}{{returnType}}{{/returnType}}{{^returnType}}None{{/returnType}} + If the method is called asynchronously, + returns the request thread. + """ + kwargs['_return_http_data_only'] = True + if kwargs.get('async_req'): + return self.{{operationId}}_with_http_info({{#sortParamsByRequiredFlag}}{{#parameters}}{{#required}}{{paramName}}, {{/required}}{{/parameters}}{{/sortParamsByRequiredFlag}}**kwargs) # noqa: E501 + else: + (data) = self.{{operationId}}_with_http_info({{#sortParamsByRequiredFlag}}{{#parameters}}{{#required}}{{paramName}}, {{/required}}{{/parameters}}{{/sortParamsByRequiredFlag}}**kwargs) # noqa: E501 + return data + + def {{operationId}}_with_http_info(self, {{#sortParamsByRequiredFlag}}{{#parameters}}{{#required}}{{paramName}}, {{/required}}{{/parameters}}{{/sortParamsByRequiredFlag}}**kwargs): # noqa: E501 + """{{#summary}}{{{.}}}{{/summary}}{{^summary}}{{operationId}}{{/summary}} # noqa: E501 + +{{#notes}} + {{{notes}}} # noqa: E501 +{{/notes}} + This method makes a synchronous HTTP request by default. To make an + asynchronous HTTP request, please pass async_req=True +{{#sortParamsByRequiredFlag}} + >>> thread = api.{{operationId}}_with_http_info({{#parameters}}{{#required}}{{paramName}}, {{/required}}{{/parameters}}async_req=True) +{{/sortParamsByRequiredFlag}} +{{^sortParamsByRequiredFlag}} + >>> thread = api.{{operationId}}_with_http_info({{#parameters}}{{#required}}{{paramName}}={{paramName}}_value, {{/required}}{{/parameters}}async_req=True) +{{/sortParamsByRequiredFlag}} + >>> result = thread.get() + + :param async_req bool +{{#parameters}} + :param {{dataType}} {{paramName}}:{{#description}} {{{description}}}{{/description}}{{#required}} (required){{/required}}{{#optional}}(optional{{#defaultValue}}, default to {{{.}}}{{/defaultValue}}){{/optional}} +{{/parameters}} + :return: {{#returnType}}{{returnType}}{{/returnType}}{{^returnType}}None{{/returnType}} + If the method is called asynchronously, + returns the request thread. + """ + + all_params = [{{#parameters}}'{{paramName}}'{{#hasMore}}, {{/hasMore}}{{/parameters}}] # noqa: E501 + all_params.append('async_req') + all_params.append('_return_http_data_only') + all_params.append('_preload_content') + all_params.append('_request_timeout') + + params = locals() + for key, val in six.iteritems(params['kwargs']): + if key not in all_params: + raise TypeError( + "Got an unexpected keyword argument '%s'" + " to method {{operationId}}" % key + ) + params[key] = val + del params['kwargs'] +{{#parameters}} +{{#required}} + # verify the required parameter '{{paramName}}' is set + if ('{{paramName}}' not in params or + params['{{paramName}}'] is None): + raise ValueError("Missing the required parameter `{{paramName}}` when calling `{{operationId}}`") # noqa: E501 +{{/required}} +{{/parameters}} + +{{#parameters}} +{{#hasValidation}} + {{#maxLength}} + if ('{{paramName}}' in params and + len(params['{{paramName}}']) > {{maxLength}}): + raise ValueError("Invalid value for parameter `{{paramName}}` when calling `{{operationId}}`, length must be less than or equal to `{{maxLength}}`") # noqa: E501 + {{/maxLength}} + {{#minLength}} + if ('{{paramName}}' in params and + len(params['{{paramName}}']) < {{minLength}}): + raise ValueError("Invalid value for parameter `{{paramName}}` when calling `{{operationId}}`, length must be greater than or equal to `{{minLength}}`") # noqa: E501 + {{/minLength}} + {{#maximum}} + if '{{paramName}}' in params and params['{{paramName}}'] >{{#exclusiveMaximum}}={{/exclusiveMaximum}} {{maximum}}: # noqa: E501 + raise ValueError("Invalid value for parameter `{{paramName}}` when calling `{{operationId}}`, must be a value less than {{^exclusiveMaximum}}or equal to {{/exclusiveMaximum}}`{{maximum}}`") # noqa: E501 + {{/maximum}} + {{#minimum}} + if '{{paramName}}' in params and params['{{paramName}}'] <{{#exclusiveMinimum}}={{/exclusiveMinimum}} {{minimum}}: # noqa: E501 + raise ValueError("Invalid value for parameter `{{paramName}}` when calling `{{operationId}}`, must be a value greater than {{^exclusiveMinimum}}or equal to {{/exclusiveMinimum}}`{{minimum}}`") # noqa: E501 + {{/minimum}} + {{#pattern}} + if '{{paramName}}' in params and not re.search(r'{{{vendorExtensions.x-regex}}}', params['{{paramName}}']{{#vendorExtensions.x-modifiers}}{{#@first}}, flags={{/@first}}re.{{.}}{{^@last}} | {{/@last}}{{/vendorExtensions.x-modifiers}}): # noqa: E501 + raise ValueError("Invalid value for parameter `{{paramName}}` when calling `{{operationId}}`, must conform to the pattern `{{{pattern}}}`") # noqa: E501 + {{/pattern}} + {{#maxItems}} + if ('{{paramName}}' in params and + len(params['{{paramName}}']) > {{maxItems}}): + raise ValueError("Invalid value for parameter `{{paramName}}` when calling `{{operationId}}`, number of items must be less than or equal to `{{maxItems}}`") # noqa: E501 + {{/maxItems}} + {{#minItems}} + if ('{{paramName}}' in params and + len(params['{{paramName}}']) < {{minItems}}): + raise ValueError("Invalid value for parameter `{{paramName}}` when calling `{{operationId}}`, number of items must be greater than or equal to `{{minItems}}`") # noqa: E501 + {{/minItems}} +{{/hasValidation}} +{{#@last}} +{{/@last}} +{{/parameters}} + collection_formats = {} + + path_params = {} +{{#pathParams}} + if '{{paramName}}' in params: + path_params['{{baseName}}'] = params['{{paramName}}']{{#isListContainer}} # noqa: E501 + collection_formats['{{baseName}}'] = '{{collectionFormat}}'{{/isListContainer}} # noqa: E501 +{{/pathParams}} + + query_params = [] +{{#queryParams}} + if '{{paramName}}' in params: + query_params.append(('{{baseName}}', params['{{paramName}}'])){{#isListContainer}} # noqa: E501 + collection_formats['{{baseName}}'] = '{{collectionFormat}}'{{/isListContainer}} # noqa: E501 +{{/queryParams}} + + header_params = {} +{{#headerParams}} + if '{{paramName}}' in params: + header_params['{{baseName}}'] = params['{{paramName}}']{{#isListContainer}} # noqa: E501 + collection_formats['{{baseName}}'] = '{{collectionFormat}}'{{/isListContainer}} # noqa: E501 +{{/headerParams}} + + form_params = [] + local_var_files = {} +{{#formParams}} + if '{{paramName}}' in params: + {{#notFile}}form_params.append(('{{baseName}}', params['{{paramName}}'])){{/notFile}}{{#isFile}}local_var_files['{{baseName}}'] = params['{{paramName}}']{{/isFile}}{{#isListContainer}} # noqa: E501 + collection_formats['{{baseName}}'] = '{{collectionFormat}}'{{/isListContainer}} # noqa: E501 +{{/formParams}} + + body_params = None +{{#bodyParam}} + if '{{paramName}}' in params: + body_params = params['{{paramName}}'] +{{/bodyParam}} + {{#hasProduces}} + # HTTP header `Accept` + header_params['Accept'] = self.api_client.select_header_accept( + [{{#produces}}'{{{mediaType}}}'{{#hasMore}}, {{/hasMore}}{{/produces}}]) # noqa: E501 + + {{/hasProduces}} + {{#hasConsumes}} + # HTTP header `Content-Type` + header_params['Content-Type'] = self.api_client.select_header_content_type( # noqa: E501 + [{{#consumes}}'{{{mediaType}}}'{{#hasMore}}, {{/hasMore}}{{/consumes}}]) # noqa: E501 + + {{/hasConsumes}} + # Authentication setting + auth_settings = [{{#authMethods}}'{{name}}'{{#hasMore}}, {{/hasMore}}{{/authMethods}}] # noqa: E501 + + return self.api_client.call_api( + '{{{path}}}', '{{httpMethod}}', + path_params, + query_params, + header_params, + body=body_params, + post_params=form_params, + files=local_var_files, + response_type={{#returnType}}'{{returnType}}'{{/returnType}}{{^returnType}}None{{/returnType}}, # noqa: E501 + auth_settings=auth_settings, + async_req=params.get('async_req'), + _return_http_data_only=params.get('_return_http_data_only'), + _preload_content=params.get('_preload_content', True), + _request_timeout=params.get('_request_timeout'), + collection_formats=collection_formats) +{{/contents}} +{{/operation}} +{{/operations}} diff --git a/src/main/resources/handlebars/python/api_client.mustache b/src/main/resources/handlebars/python/api_client.mustache new file mode 100644 index 0000000000..e46230fd75 --- /dev/null +++ b/src/main/resources/handlebars/python/api_client.mustache @@ -0,0 +1,635 @@ +# coding: utf-8 +{{>partial_header}} +from __future__ import absolute_import + +import datetime +import json +import mimetypes +from multiprocessing.pool import ThreadPool +import os +import re +import tempfile + +# python 2 and python 3 compatibility library +import six +from six.moves.urllib.parse import quote +{{#tornado}} +import tornado.gen +{{/tornado}} + +from {{packageName}}.configuration import Configuration +import {{modelPackage}} +from {{packageName}} import rest + + +class ApiClient(object): + """Generic API client for Swagger client library builds. + + Swagger generic API client. This client handles the client- + server communication, and is invariant across implementations. Specifics of + the methods and models for each application are generated from the Swagger + templates. + + NOTE: This class is auto generated by the swagger code generator program. + Ref: https://github.com/swagger-api/swagger-codegen + Do not edit the class manually. + + :param configuration: .Configuration object for this client + :param header_name: a header to pass when making calls to the API. + :param header_value: a header value to pass when making calls to + the API. + :param cookie: a cookie to include in the header when making calls + to the API + """ + + PRIMITIVE_TYPES = (float, bool, bytes, six.text_type) + six.integer_types + NATIVE_TYPES_MAPPING = { + 'int': int, + 'long': int if six.PY3 else long, # noqa: F821 + 'float': float, + 'str': str, + 'bool': bool, + 'date': datetime.date, + 'datetime': datetime.datetime, + 'object': object, + } + + def __init__(self, configuration=None, header_name=None, header_value=None, + cookie=None): + if configuration is None: + configuration = Configuration() + self.configuration = configuration + + self.pool = ThreadPool() + self.rest_client = rest.RESTClientObject(configuration) + self.default_headers = {} + if header_name is not None: + self.default_headers[header_name] = header_value + self.cookie = cookie + # Set default User-Agent. + self.user_agent = '{{#httpUserAgent}}{{{.}}}{{/httpUserAgent}}{{^httpUserAgent}}Swagger-Codegen/{{{packageVersion}}}/python{{/httpUserAgent}}' + + def __del__(self): + self.pool.close() + self.pool.join() + + @property + def user_agent(self): + """User agent for this API client""" + return self.default_headers['User-Agent'] + + @user_agent.setter + def user_agent(self, value): + self.default_headers['User-Agent'] = value + + def set_default_header(self, header_name, header_value): + self.default_headers[header_name] = header_value + + {{#tornado}} + @tornado.gen.coroutine + {{/tornado}} + {{#asyncio}}async {{/asyncio}}def __call_api( + self, resource_path, method, path_params=None, + query_params=None, header_params=None, body=None, post_params=None, + files=None, response_type=None, auth_settings=None, + _return_http_data_only=None, collection_formats=None, + _preload_content=True, _request_timeout=None): + + config = self.configuration + + # header parameters + header_params = header_params or {} + header_params.update(self.default_headers) + if self.cookie: + header_params['Cookie'] = self.cookie + if header_params: + header_params = self.sanitize_for_serialization(header_params) + header_params = dict(self.parameters_to_tuples(header_params, + collection_formats)) + + # path parameters + if path_params: + path_params = self.sanitize_for_serialization(path_params) + path_params = self.parameters_to_tuples(path_params, + collection_formats) + for k, v in path_params: + # specified safe chars, encode everything + resource_path = resource_path.replace( + '{%s}' % k, + quote(str(v), safe=config.safe_chars_for_path_param) + ) + + # query parameters + if query_params: + query_params = self.sanitize_for_serialization(query_params) + query_params = self.parameters_to_tuples(query_params, + collection_formats) + + # post parameters + if post_params or files: + post_params = self.prepare_post_parameters(post_params, files) + post_params = self.sanitize_for_serialization(post_params) + post_params = self.parameters_to_tuples(post_params, + collection_formats) + + # auth setting + self.update_params_for_auth(header_params, query_params, auth_settings) + + # body + if body: + body = self.sanitize_for_serialization(body) + + # request url + url = self.configuration.host + resource_path + + # perform request and return response + response_data = {{#asyncio}}await {{/asyncio}}{{#tornado}}yield {{/tornado}}self.request( + method, url, query_params=query_params, headers=header_params, + post_params=post_params, body=body, + _preload_content=_preload_content, + _request_timeout=_request_timeout) + + self.last_response = response_data + + return_data = response_data + if _preload_content: + # deserialize response data + if response_type: + return_data = self.deserialize(response_data, response_type) + else: + return_data = None + +{{^tornado}} + if _return_http_data_only: + return (return_data) + else: + return (return_data, response_data.status, + response_data.getheaders()) +{{/tornado}} +{{#tornado}} + if _return_http_data_only: + raise tornado.gen.Return(return_data) + else: + raise tornado.gen.Return((return_data, response_data.status, + response_data.getheaders())) +{{/tornado}} + + def sanitize_for_serialization(self, obj): + """Builds a JSON POST object. + + If obj is None, return None. + If obj is str, int, long, float, bool, return directly. + If obj is datetime.datetime, datetime.date + convert to string in iso8601 format. + If obj is list, sanitize each element in the list. + If obj is dict, return the dict. + If obj is swagger model, return the properties dict. + + :param obj: The data to serialize. + :return: The serialized form of data. + """ + if obj is None: + return None + elif isinstance(obj, self.PRIMITIVE_TYPES): + return obj + elif isinstance(obj, list): + return [self.sanitize_for_serialization(sub_obj) + for sub_obj in obj] + elif isinstance(obj, tuple): + return tuple(self.sanitize_for_serialization(sub_obj) + for sub_obj in obj) + elif isinstance(obj, (datetime.datetime, datetime.date)): + return obj.isoformat() + + if isinstance(obj, dict): + obj_dict = obj + else: + # Convert model obj to dict except + # attributes `swagger_types`, `attribute_map` + # and attributes which value is not None. + # Convert attribute name to json key in + # model definition for request. + obj_dict = {obj.attribute_map[attr]: getattr(obj, attr) + for attr, _ in six.iteritems(obj.swagger_types) + if getattr(obj, attr) is not None} + + return {key: self.sanitize_for_serialization(val) + for key, val in six.iteritems(obj_dict)} + + def deserialize(self, response, response_type): + """Deserializes response into an object. + + :param response: RESTResponse object to be deserialized. + :param response_type: class literal for + deserialized object, or string of class name. + + :return: deserialized object. + """ + # handle file downloading + # save response body into a tmp file and return the instance + if response_type == "file": + return self.__deserialize_file(response) + + # fetch data from response object + try: + data = json.loads(response.data) + except ValueError: + data = response.data + + return self.__deserialize(data, response_type) + + def __deserialize(self, 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 type(klass) == str: + if klass.startswith('list['): + sub_kls = re.match(r'list\[(.*)\]', klass).group(1) + return [self.__deserialize(sub_data, sub_kls) + for sub_data in data] + + if klass.startswith('dict('): + sub_kls = re.match(r'dict\(([^,]*), (.*)\)', klass).group(2) + return {k: self.__deserialize(v, sub_kls) + for k, v in six.iteritems(data)} + + # convert str to class + if klass in self.NATIVE_TYPES_MAPPING: + klass = self.NATIVE_TYPES_MAPPING[klass] + else: + klass = getattr({{modelPackage}}, klass) + + if klass in self.PRIMITIVE_TYPES: + return self.__deserialize_primitive(data, klass) + elif klass == object: + return self.__deserialize_object(data) + elif klass == datetime.date: + return self.__deserialize_date(data) + elif klass == datetime.datetime: + return self.__deserialize_datatime(data) + else: + return self.__deserialize_model(data, klass) + + def call_api(self, resource_path, method, + path_params=None, query_params=None, header_params=None, + body=None, post_params=None, files=None, + response_type=None, auth_settings=None, async_req=None, + _return_http_data_only=None, collection_formats=None, + _preload_content=True, _request_timeout=None): + """Makes the HTTP request (synchronous) and returns deserialized data. + + To make an async request, set the async_req parameter. + + :param resource_path: Path to method endpoint. + :param method: Method to call. + :param path_params: Path parameters in the url. + :param query_params: Query parameters in the url. + :param header_params: Header parameters to be + placed in the request header. + :param body: Request body. + :param post_params dict: Request post form parameters, + for `application/x-www-form-urlencoded`, `multipart/form-data`. + :param auth_settings list: Auth Settings names for the request. + :param response: Response data type. + :param files dict: key -> filename, value -> filepath, + for `multipart/form-data`. + :param async_req bool: execute request asynchronously + :param _return_http_data_only: response data without head status code + and headers + :param collection_formats: dict of collection formats for path, query, + header, and post parameters. + :param _preload_content: if False, the urllib3.HTTPResponse object will + be returned without reading/decoding response + data. Default is True. + :param _request_timeout: timeout setting for this request. If one + number provided, it will be total request + timeout. It can also be a pair (tuple) of + (connection, read) timeouts. + :return: + If async_req parameter is True, + the request will be called asynchronously. + The method will return the request thread. + If parameter async_req is False or missing, + then the method will return the response directly. + """ + if not async_req: + return self.__call_api(resource_path, method, + path_params, query_params, header_params, + body, post_params, files, + response_type, auth_settings, + _return_http_data_only, collection_formats, + _preload_content, _request_timeout) + else: + thread = self.pool.apply_async(self.__call_api, (resource_path, + method, path_params, query_params, + header_params, body, + post_params, files, + response_type, auth_settings, + _return_http_data_only, + collection_formats, + _preload_content, _request_timeout)) + return thread + + def request(self, method, url, query_params=None, headers=None, + post_params=None, body=None, _preload_content=True, + _request_timeout=None): + """Makes the HTTP request using RESTClient.""" + if method == "GET": + return self.rest_client.GET(url, + query_params=query_params, + _preload_content=_preload_content, + _request_timeout=_request_timeout, + headers=headers) + elif method == "HEAD": + return self.rest_client.HEAD(url, + query_params=query_params, + _preload_content=_preload_content, + _request_timeout=_request_timeout, + headers=headers) + elif method == "OPTIONS": + return self.rest_client.OPTIONS(url, + query_params=query_params, + headers=headers, + post_params=post_params, + _preload_content=_preload_content, + _request_timeout=_request_timeout, + body=body) + elif method == "POST": + return self.rest_client.POST(url, + query_params=query_params, + headers=headers, + post_params=post_params, + _preload_content=_preload_content, + _request_timeout=_request_timeout, + body=body) + elif method == "PUT": + return self.rest_client.PUT(url, + query_params=query_params, + headers=headers, + post_params=post_params, + _preload_content=_preload_content, + _request_timeout=_request_timeout, + body=body) + elif method == "PATCH": + return self.rest_client.PATCH(url, + query_params=query_params, + headers=headers, + post_params=post_params, + _preload_content=_preload_content, + _request_timeout=_request_timeout, + body=body) + elif method == "DELETE": + return self.rest_client.DELETE(url, + query_params=query_params, + headers=headers, + _preload_content=_preload_content, + _request_timeout=_request_timeout, + body=body) + else: + raise ValueError( + "http method must be `GET`, `HEAD`, `OPTIONS`," + " `POST`, `PATCH`, `PUT` or `DELETE`." + ) + + def parameters_to_tuples(self, params, collection_formats): + """Get parameters as list of tuples, formatting collections. + + :param params: Parameters as dict or list of two-tuples + :param dict collection_formats: Parameter collection formats + :return: Parameters as list of tuples, collections formatted + """ + new_params = [] + if collection_formats is None: + collection_formats = {} + for k, v in six.iteritems(params) if isinstance(params, dict) else params: # noqa: E501 + if k in collection_formats: + collection_format = collection_formats[k] + if collection_format == 'multi': + new_params.extend((k, value) for value in v) + else: + if collection_format == 'ssv': + delimiter = ' ' + elif collection_format == 'tsv': + delimiter = '\t' + elif collection_format == 'pipes': + delimiter = '|' + else: # csv is the default + delimiter = ',' + new_params.append( + (k, delimiter.join(str(value) for value in v))) + else: + new_params.append((k, v)) + return new_params + + def prepare_post_parameters(self, post_params=None, files=None): + """Builds form parameters. + + :param post_params: Normal form parameters. + :param files: File parameters. + :return: Form parameters with files. + """ + params = [] + + if post_params: + params = post_params + + if files: + for k, v in six.iteritems(files): + if not v: + continue + file_names = v if type(v) is list else [v] + for n in file_names: + with open(n, 'rb') as f: + filename = os.path.basename(f.name) + filedata = f.read() + mimetype = (mimetypes.guess_type(filename)[0] or + 'application/octet-stream') + params.append( + tuple([k, tuple([filename, filedata, mimetype])])) + + return params + + def select_header_accept(self, accepts): + """Returns `Accept` based on an array of accepts provided. + + :param accepts: List of headers. + :return: Accept (e.g. application/json). + """ + if not accepts: + return + + accepts = [x.lower() for x in accepts] + + if 'application/json' in accepts: + return 'application/json' + else: + return ', '.join(accepts) + + def select_header_content_type(self, content_types): + """Returns `Content-Type` based on an array of content_types provided. + + :param content_types: List of content-types. + :return: Content-Type (e.g. application/json). + """ + if not content_types: + return 'application/json' + + content_types = [x.lower() for x in content_types] + + if 'application/json' in content_types or '*/*' in content_types: + return 'application/json' + else: + return content_types[0] + + def update_params_for_auth(self, headers, querys, auth_settings): + """Updates header and query params based on authentication setting. + + :param headers: Header parameters dict to be updated. + :param querys: Query parameters tuple list to be updated. + :param auth_settings: Authentication setting identifiers list. + """ + if not auth_settings: + return + + for auth in auth_settings: + auth_setting = self.configuration.auth_settings().get(auth) + if auth_setting: + if not auth_setting['value']: + continue + elif auth_setting['in'] == 'header': + headers[auth_setting['key']] = auth_setting['value'] + elif auth_setting['in'] == 'query': + querys.append((auth_setting['key'], auth_setting['value'])) + else: + raise ValueError( + 'Authentication token must be in `query` or `header`' + ) + + def __deserialize_file(self, response): + """Deserializes body to file + + Saves response body into a file in a temporary folder, + using the filename from the `Content-Disposition` header if provided. + + :param response: RESTResponse. + :return: file path. + """ + fd, path = tempfile.mkstemp(dir=self.configuration.temp_folder_path) + os.close(fd) + os.remove(path) + + content_disposition = response.getheader("Content-Disposition") + if content_disposition: + filename = re.search(r'filename=[\'"]?([^\'"\s]+)[\'"]?', + content_disposition).group(1) + path = os.path.join(os.path.dirname(path), filename) + + with open(path, "wb") as f: + f.write(response.data) + + return path + + def __deserialize_primitive(self, data, klass): + """Deserializes string to primitive type. + + :param data: str. + :param klass: class literal. + + :return: int, long, float, str, bool. + """ + try: + return klass(data) + except UnicodeEncodeError: + return six.text_type(data) + except TypeError: + return data + + def __deserialize_object(self, value): + """Return a original value. + + :return: object. + """ + return value + + def __deserialize_date(self, string): + """Deserializes string to date. + + :param string: str. + :return: date. + """ + try: + from dateutil.parser import parse + return parse(string).date() + except ImportError: + return string + except ValueError: + raise rest.ApiException( + status=0, + reason="Failed to parse `{0}` as date object".format(string) + ) + + def __deserialize_datatime(self, string): + """Deserializes string to datetime. + + The string should be in iso8601 datetime format. + + :param string: str. + :return: datetime. + """ + try: + from dateutil.parser import parse + return parse(string) + except ImportError: + return string + except ValueError: + raise rest.ApiException( + status=0, + reason=( + "Failed to parse `{0}` as datetime object" + .format(string) + ) + ) + + def __hasattr(self, object, name): + return name in object.__class__.__dict__ + + def __deserialize_model(self, data, klass): + """Deserializes list or dict to model. + + :param data: dict, list. + :param klass: class literal. + :return: model object. + """ + + if not klass.swagger_types and not self.__hasattr(klass, 'get_real_child_model'): + return data + + kwargs = {} + if klass.swagger_types is not None: + for attr, attr_type in six.iteritems(klass.swagger_types): + if (data is not None and + klass.attribute_map[attr] in data and + isinstance(data, (list, dict))): + value = data[klass.attribute_map[attr]] + kwargs[attr] = self.__deserialize(value, attr_type) + + instance = klass(**kwargs) + + if (isinstance(instance, dict) and + klass.swagger_types is not None and + isinstance(data, dict)): + for key, value in data.items(): + if key not in klass.swagger_types: + instance[key] = value + if self.__hasattr(instance, 'get_real_child_model'): + klass_name = instance.get_real_child_model(data) + if klass_name: + instance = self.__deserialize(data, klass_name) + return instance diff --git a/src/main/resources/handlebars/python/api_doc.mustache b/src/main/resources/handlebars/python/api_doc.mustache new file mode 100644 index 0000000000..1382f59f12 --- /dev/null +++ b/src/main/resources/handlebars/python/api_doc.mustache @@ -0,0 +1,89 @@ +# {{packageName}}.{{classname}}{{#description}} +{{description}}{{/description}} + +All URIs are relative to *{{basePath}}* + +Method | HTTP request | Description +------------- | ------------- | ------------- +{{#operations}}{{#operation}}[**{{operationId}}**]({{classname}}.md#{{operationId}}) | **{{httpMethod}}** {{path}} | {{#summary}}{{summary}}{{/summary}} +{{/operation}}{{/operations}} + +{{#operations}} +{{#operation}} +{{#contents}} +{{#@first}} +# **{{{operationId}}}** +> {{#returnType}}{{{returnType}}} {{/returnType}}{{{operationId}}}({{#parameters}}{{#required}}{{{paramName}}}{{/required}}{{^required}}{{{paramName}}}={{{paramName}}}{{/required}}{{#hasMore}}, {{/hasMore}}{{/parameters}}) + +{{{summary}}}{{#notes}} + +{{{notes}}}{{/notes}} + +### Example +```python +from __future__ import print_function +import time +import {{{packageName}}} +from {{{packageName}}}.rest import ApiException +from pprint import pprint +{{#hasAuthMethods}}{{#authMethods}}{{#isBasic}} +# Configure HTTP basic authorization: {{{name}}} +configuration = {{{packageName}}}.Configuration() +configuration.username = 'YOUR_USERNAME' +configuration.password = 'YOUR_PASSWORD'{{/isBasic}}{{#isApiKey}} +# Configure API key authorization: {{{name}}} +configuration = {{{packageName}}}.Configuration() +configuration.api_key['{{{keyParamName}}}'] = 'YOUR_API_KEY' +# Uncomment below to setup prefix (e.g. Bearer) for API key, if needed +# configuration.api_key_prefix['{{{keyParamName}}}'] = 'Bearer'{{/isApiKey}}{{#isOAuth}} +# Configure OAuth2 access token for authorization: {{{name}}} +configuration = {{{packageName}}}.Configuration() +configuration.access_token = 'YOUR_ACCESS_TOKEN'{{/isOAuth}}{{/authMethods}} + +# create an instance of the API class +api_instance = {{{packageName}}}.{{{classname}}}({{{packageName}}}.ApiClient(configuration)) +{{#parameters}}{{paramName}} = {{{example}}} # {{{dataType}}} | {{{description}}}{{^required}} (optional){{/required}}{{#defaultValue}} (default to {{{.}}}){{/defaultValue}} +{{/parameters}} +{{/hasAuthMethods}} +{{^hasAuthMethods}} + +# create an instance of the API class +api_instance = {{{packageName}}}.{{{classname}}}() +{{#parameters}}{{paramName}} = {{{example}}} # {{{dataType}}} | {{{description}}}{{^required}} (optional){{/required}}{{#defaultValue}} (default to {{{.}}}){{/defaultValue}} +{{/parameters}} +{{/hasAuthMethods}} + +try: +{{#summary}} # {{{.}}} +{{/summary}} {{#returnType}}api_response = {{/returnType}}api_instance.{{{operationId}}}({{#parameters}}{{#required}}{{paramName}}{{/required}}{{^required}}{{paramName}}={{paramName}}{{/required}}{{#hasMore}}, {{/hasMore}}{{/parameters}}){{#returnType}} + pprint(api_response){{/returnType}} +except ApiException as e: + print("Exception when calling {{classname}}->{{operationId}}: %s\n" % e) +``` + +### Parameters +{{^parameters}}This endpoint does not need any parameter.{{/parameters}}{{#parameters}}{{#@last}} +Name | Type | Description | Notes +------------- | ------------- | ------------- | -------------{{/@last}}{{/parameters}} +{{#parameters}} **{{paramName}}** | {{#isFile}}**{{dataType}}**{{/isFile}}{{^isFile}}{{#isPrimitiveType}}**{{dataType}}**{{/isPrimitiveType}}{{^isPrimitiveType}}[**{{dataType}}**]({{baseType}}.md){{/isPrimitiveType}}{{/isFile}}| {{description}} | {{^required}}[optional] {{/required}}{{#defaultValue}}[default to {{defaultValue}}]{{/defaultValue}} +{{/parameters}} + +### Return type + +{{#returnType}}{{#returnTypeIsPrimitive}}**{{{returnType}}}**{{/returnTypeIsPrimitive}}{{^returnTypeIsPrimitive}}[**{{{returnType}}}**]({{returnBaseType}}.md){{/returnTypeIsPrimitive}}{{/returnType}}{{^returnType}}void (empty response body){{/returnType}} + +### Authorization + +{{^authMethods}}No authorization required{{/authMethods}}{{#authMethods}}[{{{name}}}](../README.md#{{{name}}}){{^@last}}, {{/@last}}{{/authMethods}} + +### HTTP request headers + + - **Content-Type**: {{#consumes}}{{{mediaType}}}{{#hasMore}}, {{/hasMore}}{{/consumes}}{{^consumes}}Not defined{{/consumes}} + - **Accept**: {{#produces}}{{{mediaType}}}{{#hasMore}}, {{/hasMore}}{{/produces}}{{^produces}}Not defined{{/produces}} + +[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) + +{{/@first}} +{{/contents}} +{{/operation}} +{{/operations}} diff --git a/src/main/resources/handlebars/python/api_test.mustache b/src/main/resources/handlebars/python/api_test.mustache new file mode 100644 index 0000000000..90fafc15f3 --- /dev/null +++ b/src/main/resources/handlebars/python/api_test.mustache @@ -0,0 +1,37 @@ +# coding: utf-8 + +{{>partial_header}} + +from __future__ import absolute_import + +import unittest + +import {{packageName}} +from {{apiPackage}}.{{classVarName}} import {{classname}} # noqa: E501 +from {{packageName}}.rest import ApiException + + +class {{#operations}}Test{{classname}}(unittest.TestCase): + """{{classname}} unit test stubs""" + + def setUp(self): + self.api = {{apiPackage}}.{{classVarName}}.{{classname}}() # noqa: E501 + + def tearDown(self): + pass + + {{#operation}} + def test_{{operationId}}(self): + """Test case for {{{operationId}}} + +{{#summary}} + {{{summary}}} # noqa: E501 +{{/summary}} + """ + pass + + {{/operation}} +{{/operations}} + +if __name__ == '__main__': + unittest.main() diff --git a/src/main/resources/handlebars/python/asyncio/rest.mustache b/src/main/resources/handlebars/python/asyncio/rest.mustache new file mode 100644 index 0000000000..39003abfbf --- /dev/null +++ b/src/main/resources/handlebars/python/asyncio/rest.mustache @@ -0,0 +1,260 @@ +# coding: utf-8 + +{{>partial_header}} + +import io +import json +import logging +import re +import ssl + +import aiohttp +import certifi +# python 2 and python 3 compatibility library +from six.moves.urllib.parse import urlencode + +logger = logging.getLogger(__name__) + + +class RESTResponse(io.IOBase): + + def __init__(self, resp, data): + self.aiohttp_response = resp + self.status = resp.status + self.reason = resp.reason + self.data = data + + def getheaders(self): + """Returns a CIMultiDictProxy of the response headers.""" + return self.aiohttp_response.headers + + def getheader(self, name, default=None): + """Returns a given response header.""" + return self.aiohttp_response.headers.get(name, default) + + +class RESTClientObject(object): + + def __init__(self, configuration, pools_size=4, maxsize=4): + # maxsize is number of requests to host that are allowed in parallel + # ca_certs vs cert_file vs key_file + # http://stackoverflow.com/a/23957365/2985775 + + # ca_certs + if configuration.ssl_ca_cert: + ca_certs = configuration.ssl_ca_cert + else: + # if not set certificate file, use Mozilla's root certificates. + ca_certs = certifi.where() + + ssl_context = ssl.create_default_context(cafile=ca_certs) + if configuration.cert_file: + ssl_context.load_cert_chain( + configuration.cert_file, keyfile=configuration.key_file + ) + + connector = aiohttp.TCPConnector( + limit=maxsize, + ssl_context=ssl_context, + verify_ssl=configuration.verify_ssl + ) + + # https pool manager + if configuration.proxy: + self.pool_manager = aiohttp.ClientSession( + connector=connector, + proxy=configuration.proxy + ) + else: + self.pool_manager = aiohttp.ClientSession( + connector=connector + ) + + async def request(self, method, url, query_params=None, headers=None, + body=None, post_params=None, _preload_content=True, + _request_timeout=None): + """Execute request + + :param method: http request method + :param url: http request url + :param query_params: query parameters in the url + :param headers: http request headers + :param body: request json body, for `application/json` + :param post_params: request post parameters, + `application/x-www-form-urlencoded` + and `multipart/form-data` + :param _preload_content: this is a non-applicable field for + the AiohttpClient. + :param _request_timeout: timeout setting for this request. If one + number provided, it will be total request + timeout. It can also be a pair (tuple) of + (connection, read) timeouts. + """ + method = method.upper() + assert method in ['GET', 'HEAD', 'DELETE', 'POST', 'PUT', + 'PATCH', 'OPTIONS'] + + if post_params and body: + raise ValueError( + "body parameter cannot be used with post_params parameter." + ) + + post_params = post_params or {} + headers = headers or {} + timeout = _request_timeout or 5 * 60 + + if 'Content-Type' not in headers: + headers['Content-Type'] = 'application/json' + + args = { + "method": method, + "url": url, + "timeout": timeout, + "headers": headers + } + + if query_params: + args["url"] += '?' + urlencode(query_params) + + # For `POST`, `PUT`, `PATCH`, `OPTIONS`, `DELETE` + if method in ['POST', 'PUT', 'PATCH', 'OPTIONS', 'DELETE']: + if re.search('json', headers['Content-Type'], re.IGNORECASE): + if body is not None: + body = json.dumps(body) + args["data"] = body + elif headers['Content-Type'] == 'application/x-www-form-urlencoded': # noqa: E501 + args["data"] = aiohttp.FormData(post_params) + elif headers['Content-Type'] == 'multipart/form-data': + # must del headers['Content-Type'], or the correct + # Content-Type which generated by aiohttp + del headers['Content-Type'] + data = aiohttp.FormData() + for param in post_params: + k, v = param + if isinstance(v, tuple) and len(v) == 3: + data.add_field(k, + value=v[1], + filename=v[0], + content_type=v[2]) + else: + data.add_field(k, v) + args["data"] = data + + # Pass a `bytes` parameter directly in the body to support + # other content types than Json when `body` argument is provided + # in serialized form + elif isinstance(body, bytes): + args["data"] = body + else: + # Cannot generate the request from given parameters + msg = """Cannot prepare a request message for provided + arguments. Please check that your arguments match + declared content type.""" + raise ApiException(status=0, reason=msg) + + async with self.pool_manager.request(**args) as r: + data = await r.text() + r = RESTResponse(r, data) + + # log response body + logger.debug("response body: %s", r.data) + + if not 200 <= r.status <= 299: + raise ApiException(http_resp=r) + + return r + + async def GET(self, url, headers=None, query_params=None, + _preload_content=True, _request_timeout=None): + return (await self.request("GET", url, + headers=headers, + _preload_content=_preload_content, + _request_timeout=_request_timeout, + query_params=query_params)) + + async def HEAD(self, url, headers=None, query_params=None, + _preload_content=True, _request_timeout=None): + return (await self.request("HEAD", url, + headers=headers, + _preload_content=_preload_content, + _request_timeout=_request_timeout, + query_params=query_params)) + + async def OPTIONS(self, url, headers=None, query_params=None, + post_params=None, body=None, _preload_content=True, + _request_timeout=None): + return (await self.request("OPTIONS", url, + headers=headers, + query_params=query_params, + post_params=post_params, + _preload_content=_preload_content, + _request_timeout=_request_timeout, + body=body)) + + async def DELETE(self, url, headers=None, query_params=None, body=None, + _preload_content=True, _request_timeout=None): + return (await self.request("DELETE", url, + headers=headers, + query_params=query_params, + _preload_content=_preload_content, + _request_timeout=_request_timeout, + body=body)) + + async def POST(self, url, headers=None, query_params=None, + post_params=None, body=None, _preload_content=True, + _request_timeout=None): + return (await self.request("POST", url, + headers=headers, + query_params=query_params, + post_params=post_params, + _preload_content=_preload_content, + _request_timeout=_request_timeout, + body=body)) + + async def PUT(self, url, headers=None, query_params=None, post_params=None, + body=None, _preload_content=True, _request_timeout=None): + return (await self.request("PUT", url, + headers=headers, + query_params=query_params, + post_params=post_params, + _preload_content=_preload_content, + _request_timeout=_request_timeout, + body=body)) + + async def PATCH(self, url, headers=None, query_params=None, + post_params=None, body=None, _preload_content=True, + _request_timeout=None): + return (await self.request("PATCH", url, + headers=headers, + query_params=query_params, + post_params=post_params, + _preload_content=_preload_content, + _request_timeout=_request_timeout, + body=body)) + + +class ApiException(Exception): + + def __init__(self, status=None, reason=None, http_resp=None): + if http_resp: + self.status = http_resp.status + self.reason = http_resp.reason + self.body = http_resp.data + self.headers = http_resp.getheaders() + else: + self.status = status + self.reason = reason + self.body = None + self.headers = None + + def __str__(self): + """Custom error messages for exception""" + error_message = "({0})\nReason: {1}\n".format(self.status, self.reason) + if self.headers: + error_message += "HTTP response headers: {0}\n".format( + self.headers) + + if self.body: + error_message += "HTTP response body: {0}\n".format(self.body) + + return error_message diff --git a/src/main/resources/handlebars/python/configuration.mustache b/src/main/resources/handlebars/python/configuration.mustache new file mode 100644 index 0000000000..84e6ad4d20 --- /dev/null +++ b/src/main/resources/handlebars/python/configuration.mustache @@ -0,0 +1,260 @@ +# coding: utf-8 + +{{>partial_header}} + +from __future__ import absolute_import + +import copy +import logging +import multiprocessing +import sys +import urllib3 + +import six +from six.moves import http_client as httplib + + +class TypeWithDefault(type): + def __init__(cls, name, bases, dct): + super(TypeWithDefault, cls).__init__(name, bases, dct) + cls._default = None + + def __call__(cls): + if cls._default is None: + cls._default = type.__call__(cls) + return copy.copy(cls._default) + + def set_default(cls, default): + cls._default = copy.copy(default) + + +class Configuration(six.with_metaclass(TypeWithDefault, object)): + """NOTE: This class is auto generated by the swagger code generator program. + + Ref: https://github.com/swagger-api/swagger-codegen + Do not edit the class manually. + """ + + def __init__(self): + """Constructor""" + # Default Base url + self.host = "{{{basePath}}}" + # Temp file folder for downloading files + self.temp_folder_path = None + + # Authentication Settings + # dict to store API key(s) + self.api_key = {} + # dict to store API prefix (e.g. Bearer) + self.api_key_prefix = {} + # Username for HTTP basic authentication + self.username = "" + # Password for HTTP basic authentication + self.password = "" +{{#authMethods}}{{#isOAuth}} + # access token for OAuth + self.access_token = "" +{{/isOAuth}}{{/authMethods}} + # Logging Settings + self.logger = {} + self.logger["package_logger"] = logging.getLogger("{{packageName}}") + self.logger["urllib3_logger"] = logging.getLogger("urllib3") + # Log format + self.logger_format = '%(asctime)s %(levelname)s %(message)s' + # Log stream handler + self.logger_stream_handler = None + # Log file handler + self.logger_file_handler = None + # Debug file location + self.logger_file = None + # Debug switch + self.debug = False + + # SSL/TLS verification + # Set this to false to skip verifying SSL certificate when calling API + # from https server. + self.verify_ssl = True + # Set this to customize the certificate file to verify the peer. + self.ssl_ca_cert = None + # client certificate file + self.cert_file = None + # client key file + self.key_file = None + # Set this to True/False to enable/disable SSL hostname verification. + self.assert_hostname = None + + # urllib3 connection pool's maximum number of connections saved + # per pool. urllib3 uses 1 connection as default value, but this is + # not the best value when you are making a lot of possibly parallel + # requests to the same host, which is often the case here. + # cpu_count * 5 is used as default value to increase performance. + self.connection_pool_maxsize = multiprocessing.cpu_count() * 5 + + # Proxy URL + self.proxy = None + # Safe chars for path_param + self.safe_chars_for_path_param = '' + + @property + def logger_file(self): + """The logger file. + + If the logger_file is None, then add stream handler and remove file + handler. Otherwise, add file handler and remove stream handler. + + :param value: The logger_file path. + :type: str + """ + return self.__logger_file + + @logger_file.setter + def logger_file(self, value): + """The logger file. + + If the logger_file is None, then add stream handler and remove file + handler. Otherwise, add file handler and remove stream handler. + + :param value: The logger_file path. + :type: str + """ + self.__logger_file = value + if self.__logger_file: + # If set logging file, + # then add file handler and remove stream handler. + self.logger_file_handler = logging.FileHandler(self.__logger_file) + self.logger_file_handler.setFormatter(self.logger_formatter) + for _, logger in six.iteritems(self.logger): + logger.addHandler(self.logger_file_handler) + if self.logger_stream_handler: + logger.removeHandler(self.logger_stream_handler) + else: + # If not set logging file, + # then add stream handler and remove file handler. + self.logger_stream_handler = logging.StreamHandler() + self.logger_stream_handler.setFormatter(self.logger_formatter) + for _, logger in six.iteritems(self.logger): + logger.addHandler(self.logger_stream_handler) + if self.logger_file_handler: + logger.removeHandler(self.logger_file_handler) + + @property + def debug(self): + """Debug status + + :param value: The debug status, True or False. + :type: bool + """ + return self.__debug + + @debug.setter + def debug(self, value): + """Debug status + + :param value: The debug status, True or False. + :type: bool + """ + self.__debug = value + if self.__debug: + # if debug status is True, turn on debug logging + for _, logger in six.iteritems(self.logger): + logger.setLevel(logging.DEBUG) + # turn on httplib debug + httplib.HTTPConnection.debuglevel = 1 + else: + # if debug status is False, turn off debug logging, + # setting log level to default `logging.WARNING` + for _, logger in six.iteritems(self.logger): + logger.setLevel(logging.WARNING) + # turn off httplib debug + httplib.HTTPConnection.debuglevel = 0 + + @property + def logger_format(self): + """The logger format. + + The logger_formatter will be updated when sets logger_format. + + :param value: The format string. + :type: str + """ + return self.__logger_format + + @logger_format.setter + def logger_format(self, value): + """The logger format. + + The logger_formatter will be updated when sets logger_format. + + :param value: The format string. + :type: str + """ + self.__logger_format = value + self.logger_formatter = logging.Formatter(self.__logger_format) + + def get_api_key_with_prefix(self, identifier): + """Gets API key (with prefix if set). + + :param identifier: The identifier of apiKey. + :return: The token for api key authentication. + """ + if (self.api_key.get(identifier) and + self.api_key_prefix.get(identifier)): + return self.api_key_prefix[identifier] + ' ' + self.api_key[identifier] # noqa: E501 + elif self.api_key.get(identifier): + return self.api_key[identifier] + + def get_basic_auth_token(self): + """Gets HTTP basic authentication header (string). + + :return: The token for basic HTTP authentication. + """ + return urllib3.util.make_headers( + basic_auth=self.username + ':' + self.password + ).get('authorization') + + def auth_settings(self): + """Gets Auth Settings dict for api client. + + :return: The Auth Settings information dict. + """ + return { +{{#authMethods}} +{{#isApiKey}} + '{{name}}': + { + 'type': 'api_key', + 'in': {{#isKeyInHeader}}'header'{{/isKeyInHeader}}{{#isKeyInQuery}}'query'{{/isKeyInQuery}}, + 'key': '{{keyParamName}}', + 'value': self.get_api_key_with_prefix('{{keyParamName}}') + }, +{{/isApiKey}} +{{#isBasic}} + '{{name}}': + { + 'type': 'basic', + 'in': 'header', + 'key': 'Authorization', + 'value': self.get_basic_auth_token() + }, +{{/isBasic}}{{#isOAuth}} + '{{name}}': + { + 'type': 'oauth2', + 'in': 'header', + 'key': 'Authorization', + 'value': 'Bearer ' + self.access_token + }, +{{/isOAuth}}{{/authMethods}} + } + + def to_debug_report(self): + """Gets the essential information for debugging. + + :return: The report for debugging. + """ + return "Python SDK Debug Report:\n"\ + "OS: {env}\n"\ + "Python Version: {pyversion}\n"\ + "Version of the API: {{version}}\n"\ + "SDK Package Version: {{packageVersion}}".\ + format(env=sys.platform, pyversion=sys.version) diff --git a/src/main/resources/handlebars/python/git_push.sh.mustache b/src/main/resources/handlebars/python/git_push.sh.mustache new file mode 100755 index 0000000000..a2d7523483 --- /dev/null +++ b/src/main/resources/handlebars/python/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/python/gitignore.mustache b/src/main/resources/handlebars/python/gitignore.mustache new file mode 100644 index 0000000000..a655050c26 --- /dev/null +++ b/src/main/resources/handlebars/python/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/python/model.mustache b/src/main/resources/handlebars/python/model.mustache new file mode 100644 index 0000000000..d7b47edd86 --- /dev/null +++ b/src/main/resources/handlebars/python/model.mustache @@ -0,0 +1,222 @@ +# coding: utf-8 + +{{>partial_header}} + +import pprint +import re # noqa: F401 + +import six +{{#imports}}{{#@first}} +{{/@first}} +{{import}} # noqa: F401,E501 +{{/imports}} + + +{{#models}} +{{#model}} +class {{classname}}({{#parent}}{{parent}}{{/parent}}{{^parent}}object{{/parent}}): + """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}} + + """ + Attributes: + swagger_types (dict): The key is attribute name + and the value is attribute type. + attribute_map (dict): The key is attribute name + and the value is json key in definition. + """ + swagger_types = { +{{#vars}} + '{{name}}': '{{{datatype}}}'{{#hasMore}},{{/hasMore}} +{{/vars}} + } + + attribute_map = { +{{#vars}} + '{{name}}': '{{baseName}}'{{#hasMore}},{{/hasMore}} +{{/vars}} + } +{{#discriminator}} + + discriminator_value_class_map = { + {{#children}}'{{^vendorExtensions.x-discriminator-value}}{{name}}{{/vendorExtensions.x-discriminator-value}}{{#vendorExtensions.x-discriminator-value}}{{{vendorExtensions.x-discriminator-value}}}{{/vendorExtensions.x-discriminator-value}}': '{{{classname}}}'{{^@last}}, + {{/@last}}{{/children}} + } +{{/discriminator}} + + def __init__(self{{#vars}}, {{name}}={{#defaultValue}}{{{defaultValue}}}{{/defaultValue}}{{^defaultValue}}None{{/defaultValue}}{{/vars}}): # noqa: E501 + """{{classname}} - a model defined in Swagger""" # noqa: E501 +{{#vars}}{{#@first}} +{{/@first}} + self._{{name}} = None +{{/vars}} + self.discriminator = {{#discriminator}}'{{discriminator}}'{{/discriminator}}{{^discriminator}}None{{/discriminator}} +{{#vars}}{{#@first}} +{{/@first}} +{{#required}} + self.{{name}} = {{name}} +{{/required}} +{{^required}} + if {{name}} is not None: + self.{{name}} = {{name}} +{{/required}} +{{/vars}} + +{{#vars}} + @property + def {{name}}(self): + """Gets the {{name}} of this {{classname}}. # noqa: E501 + +{{#description}} + {{{description}}} # noqa: E501 +{{/description}} + + :return: The {{name}} of this {{classname}}. # noqa: E501 + :rtype: {{datatype}} + """ + return self._{{name}} + + @{{name}}.setter + def {{name}}(self, {{name}}): + """Sets the {{name}} of this {{classname}}. + +{{#description}} + {{{description}}} # noqa: E501 +{{/description}} + + :param {{name}}: The {{name}} of this {{classname}}. # noqa: E501 + :type: {{datatype}} + """ +{{#required}} + if {{name}} is None: + raise ValueError("Invalid value for `{{name}}`, must not be `None`") # noqa: E501 +{{/required}} +{{#isEnum}} +{{#isContainer}} + allowed_values = [{{#allowableValues}}{{#values}}{{#items.isString}}"{{/items.isString}}{{{this}}}{{#items.isString}}"{{/items.isString}}{{^@last}}, {{/@last}}{{/values}}{{/allowableValues}}] # noqa: E501 +{{#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}} + allowed_values = [{{#allowableValues}}{{#values}}{{#isString}}"{{/isString}}{{{this}}}{{#isString}}"{{/isString}}{{^@last}}, {{/@last}}{{/values}}{{/allowableValues}}] # noqa: E501 + if {{{name}}} not in allowed_values: + raise ValueError( + "Invalid value for `{{{name}}}` ({0}), must be one of {1}" # noqa: E501 + .format({{{name}}}, allowed_values) + ) +{{/isContainer}} +{{/isEnum}} +{{^isEnum}} +{{#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(r"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}} + +{{/vars}} +{{#discriminator}} + def get_real_child_model(self, data): + """Returns the real base class specified by the discriminator""" + discriminator_value = data[self.discriminator].lower() + return self.discriminator_value_class_map.get(discriminator_value) + +{{/discriminator}} + def to_dict(self): + """Returns the model properties as a 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 + if issubclass({{classname}}, dict): + for key, value in self.items(): + result[key] = value + + return result + + def to_str(self): + """Returns the string representation of the model""" + return pprint.pformat(self.to_dict()) + + def __repr__(self): + """For `print` and `pprint`""" + return self.to_str() + + def __eq__(self, other): + """Returns true if both objects are equal""" + if not isinstance(other, {{classname}}): + return False + + return self.__dict__ == other.__dict__ + + def __ne__(self, other): + """Returns true if both objects are not equal""" + return not self == other +{{/model}} +{{/models}} diff --git a/src/main/resources/handlebars/python/model_doc.mustache b/src/main/resources/handlebars/python/model_doc.mustache new file mode 100644 index 0000000000..569550df37 --- /dev/null +++ b/src/main/resources/handlebars/python/model_doc.mustache @@ -0,0 +1,11 @@ +{{#models}}{{#model}}# {{classname}} + +## Properties +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +{{#vars}}**{{name}}** | {{#isPrimitiveType}}**{{datatype}}**{{/isPrimitiveType}}{{^isPrimitiveType}}[**{{datatype}}**]({{complexType}}.md){{/isPrimitiveType}} | {{description}} | {{^required}}[optional] {{/required}}{{#readOnly}}[readonly] {{/readOnly}}{{#defaultValue}}[default to {{{.}}}]{{/defaultValue}} +{{/vars}} + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + +{{/model}}{{/models}} diff --git a/src/main/resources/handlebars/python/model_test.mustache b/src/main/resources/handlebars/python/model_test.mustache new file mode 100644 index 0000000000..765e4f9c2c --- /dev/null +++ b/src/main/resources/handlebars/python/model_test.mustache @@ -0,0 +1,35 @@ +# coding: utf-8 + +{{>partial_header}} + +from __future__ import absolute_import + +import unittest + +{{#models}} +{{#model}} +import {{packageName}} +from {{modelPackage}}.{{classFilename}} import {{classname}} # noqa: E501 +from {{packageName}}.rest import ApiException + + +class Test{{classname}}(unittest.TestCase): + """{{classname}} unit test stubs""" + + def setUp(self): + pass + + def tearDown(self): + pass + + def test{{classname}}(self): + """Test {{classname}}""" + # FIXME: construct object with mandatory attributes with example values + # model = {{packageName}}.models.{{classFilename}}.{{classname}}() # noqa: E501 + pass + +{{/model}} +{{/models}} + +if __name__ == '__main__': + unittest.main() diff --git a/src/main/resources/handlebars/python/partial_header.mustache b/src/main/resources/handlebars/python/partial_header.mustache new file mode 100644 index 0000000000..513ad04cdb --- /dev/null +++ b/src/main/resources/handlebars/python/partial_header.mustache @@ -0,0 +1,13 @@ +""" +{{#appName}} + {{{appName}}} +{{/appName}} + +{{#appDescription}} + {{{appDescription}}} # noqa: E501 +{{/appDescription}} + + {{#version}}OpenAPI spec version: {{{version}}}{{/version}} + {{#infoEmail}}Contact: {{{infoEmail}}}{{/infoEmail}} + Generated by: https://github.com/swagger-api/swagger-codegen.git +""" diff --git a/src/main/resources/handlebars/python/requirements.mustache b/src/main/resources/handlebars/python/requirements.mustache new file mode 100644 index 0000000000..bafdc07532 --- /dev/null +++ b/src/main/resources/handlebars/python/requirements.mustache @@ -0,0 +1,5 @@ +certifi >= 14.05.14 +six >= 1.10 +python_dateutil >= 2.5.3 +setuptools >= 21.0.0 +urllib3 >= 1.15.1 diff --git a/src/main/resources/handlebars/python/rest.mustache b/src/main/resources/handlebars/python/rest.mustache new file mode 100644 index 0000000000..ff9b019dc5 --- /dev/null +++ b/src/main/resources/handlebars/python/rest.mustache @@ -0,0 +1,314 @@ +# coding: utf-8 + +{{>partial_header}} + +from __future__ import absolute_import + +import io +import json +import logging +import re +import ssl + +import certifi +# python 2 and python 3 compatibility library +import six +from six.moves.urllib.parse import urlencode + +try: + import urllib3 +except ImportError: + raise ImportError('Swagger python client requires urllib3.') + + +logger = logging.getLogger(__name__) + + +class RESTResponse(io.IOBase): + + def __init__(self, resp): + self.urllib3_response = resp + self.status = resp.status + self.reason = resp.reason + self.data = resp.data + + def getheaders(self): + """Returns a dictionary of the response headers.""" + return self.urllib3_response.getheaders() + + def getheader(self, name, default=None): + """Returns a given response header.""" + return self.urllib3_response.getheader(name, default) + + +class RESTClientObject(object): + + def __init__(self, configuration, pools_size=4, maxsize=None): + # urllib3.PoolManager will pass all kw parameters to connectionpool + # https://github.com/shazow/urllib3/blob/f9409436f83aeb79fbaf090181cd81b784f1b8ce/urllib3/poolmanager.py#L75 # noqa: E501 + # https://github.com/shazow/urllib3/blob/f9409436f83aeb79fbaf090181cd81b784f1b8ce/urllib3/connectionpool.py#L680 # noqa: E501 + # maxsize is the number of requests to host that are allowed in parallel # noqa: E501 + # Custom SSL certificates and client certificates: http://urllib3.readthedocs.io/en/latest/advanced-usage.html # noqa: E501 + + # cert_reqs + if configuration.verify_ssl: + cert_reqs = ssl.CERT_REQUIRED + else: + cert_reqs = ssl.CERT_NONE + + # ca_certs + if configuration.ssl_ca_cert: + ca_certs = configuration.ssl_ca_cert + else: + # if not set certificate file, use Mozilla's root certificates. + ca_certs = certifi.where() + + addition_pool_args = {} + if configuration.assert_hostname is not None: + addition_pool_args['assert_hostname'] = configuration.assert_hostname # noqa: E501 + + if maxsize is None: + if configuration.connection_pool_maxsize is not None: + maxsize = configuration.connection_pool_maxsize + else: + maxsize = 4 + + # https pool manager + if configuration.proxy: + self.pool_manager = urllib3.ProxyManager( + num_pools=pools_size, + maxsize=maxsize, + cert_reqs=cert_reqs, + ca_certs=ca_certs, + cert_file=configuration.cert_file, + key_file=configuration.key_file, + proxy_url=configuration.proxy, + **addition_pool_args + ) + else: + self.pool_manager = urllib3.PoolManager( + num_pools=pools_size, + maxsize=maxsize, + cert_reqs=cert_reqs, + ca_certs=ca_certs, + cert_file=configuration.cert_file, + key_file=configuration.key_file, + **addition_pool_args + ) + + def request(self, method, url, query_params=None, headers=None, + body=None, post_params=None, _preload_content=True, + _request_timeout=None): + """Perform requests. + + :param method: http request method + :param url: http request url + :param query_params: query parameters in the url + :param headers: http request headers + :param body: request json body, for `application/json` + :param post_params: request post parameters, + `application/x-www-form-urlencoded` + and `multipart/form-data` + :param _preload_content: if False, the urllib3.HTTPResponse object will + be returned without reading/decoding response + data. Default is True. + :param _request_timeout: timeout setting for this request. If one + number provided, it will be total request + timeout. It can also be a pair (tuple) of + (connection, read) timeouts. + """ + method = method.upper() + assert method in ['GET', 'HEAD', 'DELETE', 'POST', 'PUT', + 'PATCH', 'OPTIONS'] + + if post_params and body: + raise ValueError( + "body parameter cannot be used with post_params parameter." + ) + + post_params = post_params or {} + headers = headers or {} + + timeout = None + if _request_timeout: + if isinstance(_request_timeout, (int, ) if six.PY3 else (int, long)): # noqa: E501,F821 + timeout = urllib3.Timeout(total=_request_timeout) + elif (isinstance(_request_timeout, tuple) and + len(_request_timeout) == 2): + timeout = urllib3.Timeout( + connect=_request_timeout[0], read=_request_timeout[1]) + + if 'Content-Type' not in headers: + headers['Content-Type'] = 'application/json' + + try: + # For `POST`, `PUT`, `PATCH`, `OPTIONS`, `DELETE` + if method in ['POST', 'PUT', 'PATCH', 'OPTIONS', 'DELETE']: + if query_params: + url += '?' + urlencode(query_params) + if re.search('json', headers['Content-Type'], re.IGNORECASE): + request_body = None + if body is not None: + request_body = json.dumps(body) + r = self.pool_manager.request( + method, url, + body=request_body, + preload_content=_preload_content, + timeout=timeout, + headers=headers) + elif headers['Content-Type'] == 'application/x-www-form-urlencoded': # noqa: E501 + r = self.pool_manager.request( + method, url, + fields=post_params, + encode_multipart=False, + preload_content=_preload_content, + timeout=timeout, + headers=headers) + elif headers['Content-Type'] == 'multipart/form-data': + # must del headers['Content-Type'], or the correct + # Content-Type which generated by urllib3 will be + # overwritten. + del headers['Content-Type'] + r = self.pool_manager.request( + method, url, + fields=post_params, + encode_multipart=True, + preload_content=_preload_content, + timeout=timeout, + headers=headers) + # Pass a `string` parameter directly in the body to support + # other content types than Json when `body` argument is + # provided in serialized form + elif isinstance(body, str): + request_body = body + r = self.pool_manager.request( + method, url, + body=request_body, + preload_content=_preload_content, + timeout=timeout, + headers=headers) + else: + # Cannot generate the request from given parameters + msg = """Cannot prepare a request message for provided + arguments. Please check that your arguments match + declared content type.""" + raise ApiException(status=0, reason=msg) + # For `GET`, `HEAD` + else: + r = self.pool_manager.request(method, url, + fields=query_params, + preload_content=_preload_content, + timeout=timeout, + headers=headers) + except urllib3.exceptions.SSLError as e: + msg = "{0}\n{1}".format(type(e).__name__, str(e)) + raise ApiException(status=0, reason=msg) + + if _preload_content: + r = RESTResponse(r) + + # In the python 3, the response.data is bytes. + # we need to decode it to string. + if six.PY3: + r.data = r.data.decode('utf8') + + # log response body + logger.debug("response body: %s", r.data) + + if not 200 <= r.status <= 299: + raise ApiException(http_resp=r) + + return r + + def GET(self, url, headers=None, query_params=None, _preload_content=True, + _request_timeout=None): + return self.request("GET", url, + headers=headers, + _preload_content=_preload_content, + _request_timeout=_request_timeout, + query_params=query_params) + + def HEAD(self, url, headers=None, query_params=None, _preload_content=True, + _request_timeout=None): + return self.request("HEAD", url, + headers=headers, + _preload_content=_preload_content, + _request_timeout=_request_timeout, + query_params=query_params) + + def OPTIONS(self, url, headers=None, query_params=None, post_params=None, + body=None, _preload_content=True, _request_timeout=None): + return self.request("OPTIONS", url, + headers=headers, + query_params=query_params, + post_params=post_params, + _preload_content=_preload_content, + _request_timeout=_request_timeout, + body=body) + + def DELETE(self, url, headers=None, query_params=None, body=None, + _preload_content=True, _request_timeout=None): + return self.request("DELETE", url, + headers=headers, + query_params=query_params, + _preload_content=_preload_content, + _request_timeout=_request_timeout, + body=body) + + def POST(self, url, headers=None, query_params=None, post_params=None, + body=None, _preload_content=True, _request_timeout=None): + return self.request("POST", url, + headers=headers, + query_params=query_params, + post_params=post_params, + _preload_content=_preload_content, + _request_timeout=_request_timeout, + body=body) + + def PUT(self, url, headers=None, query_params=None, post_params=None, + body=None, _preload_content=True, _request_timeout=None): + return self.request("PUT", url, + headers=headers, + query_params=query_params, + post_params=post_params, + _preload_content=_preload_content, + _request_timeout=_request_timeout, + body=body) + + def PATCH(self, url, headers=None, query_params=None, post_params=None, + body=None, _preload_content=True, _request_timeout=None): + return self.request("PATCH", url, + headers=headers, + query_params=query_params, + post_params=post_params, + _preload_content=_preload_content, + _request_timeout=_request_timeout, + body=body) + + +class ApiException(Exception): + + def __init__(self, status=None, reason=None, http_resp=None): + if http_resp: + self.status = http_resp.status + self.reason = http_resp.reason + self.body = http_resp.data + self.headers = http_resp.getheaders() + else: + self.status = status + self.reason = reason + self.body = None + self.headers = None + + def __str__(self): + """Custom error messages for exception""" + error_message = "({0})\n"\ + "Reason: {1}\n".format(self.status, self.reason) + if self.headers: + error_message += "HTTP response headers: {0}\n".format( + self.headers) + + if self.body: + error_message += "HTTP response body: {0}\n".format(self.body) + + return error_message diff --git a/src/main/resources/handlebars/python/setup.mustache b/src/main/resources/handlebars/python/setup.mustache new file mode 100644 index 0000000000..e7107e9be9 --- /dev/null +++ b/src/main/resources/handlebars/python/setup.mustache @@ -0,0 +1,43 @@ +# coding: utf-8 + +{{>partial_header}} + +from setuptools import setup, find_packages # noqa: H301 + +NAME = "{{{projectName}}}" +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 = ["urllib3 >= 1.15", "six >= 1.10", "certifi", "python-dateutil"] +{{#asyncio}} +REQUIRES.append("aiohttp") +{{/asyncio}} +{{#tornado}} +REQUIRES.append("tornado") +{{/tornado}} + +setup( + name=NAME, + version=VERSION, + description="{{appName}}", + author_email="{{infoEmail}}", + url="{{packageUrl}}", + keywords=["Swagger", "{{appName}}"], + install_requires=REQUIRES, + packages=find_packages(), + include_package_data=True, + long_description="""\ + {{appDescription}} # noqa: E501 + """ +) +{{/hasMore}} +{{/apis}} +{{/apiInfo}} diff --git a/src/main/resources/handlebars/python/test-requirements.mustache b/src/main/resources/handlebars/python/test-requirements.mustache new file mode 100644 index 0000000000..31f8d94d99 --- /dev/null +++ b/src/main/resources/handlebars/python/test-requirements.mustache @@ -0,0 +1,11 @@ +{{^asyncio}} +coverage>=4.0.3 +nose>=1.3.7 +{{/asyncio}} +{{#asyncio}} +pytest>=3.3.1 +pytest-cov>=2.5.1 +{{/asyncio}} +pluggy>=0.3.1 +py>=1.4.31 +randomize>=0.13 diff --git a/src/main/resources/handlebars/python/tornado/rest.mustache b/src/main/resources/handlebars/python/tornado/rest.mustache new file mode 100644 index 0000000000..bc74fbc295 --- /dev/null +++ b/src/main/resources/handlebars/python/tornado/rest.mustache @@ -0,0 +1,255 @@ +# coding: utf-8 + +{{>partial_header}} + +import io +import json +import logging +import re + +# python 2 and python 3 compatibility library +import six +from six.moves.urllib.parse import urlencode +import tornado +import tornado.gen +from tornado import httpclient +from urllib3.filepost import encode_multipart_formdata + +logger = logging.getLogger(__name__) + + +class RESTResponse(io.IOBase): + + def __init__(self, resp): + self.tornado_response = resp + self.status = resp.code + self.reason = resp.reason + + if resp.body: + # In Python 3, the response body is utf-8 encoded bytes. + if six.PY3: + self.data = resp.body.decode('utf-8') + else: + self.data = resp.body + else: + self.data = None + + def getheaders(self): + """Returns a CIMultiDictProxy of the response headers.""" + return self.tornado_response.headers + + def getheader(self, name, default=None): + """Returns a given response header.""" + return self.tornado_response.headers.get(name, default) + + +class RESTClientObject(object): + + def __init__(self, configuration, pools_size=4, maxsize=4): + # maxsize is number of requests to host that are allowed in parallel + + self.ca_certs = configuration.ssl_ca_cert + self.client_key = configuration.key_file + self.client_cert = configuration.cert_file + + self.proxy_port = self.proxy_host = None + + # https pool manager + if configuration.proxy: + self.proxy_port = 80 + self.proxy_host = configuration.proxy + + self.pool_manager = httpclient.AsyncHTTPClient() + + @tornado.gen.coroutine + def request(self, method, url, query_params=None, headers=None, body=None, + post_params=None, _preload_content=True, + _request_timeout=None): + """Execute Request + + :param method: http request method + :param url: http request url + :param query_params: query parameters in the url + :param headers: http request headers + :param body: request json body, for `application/json` + :param post_params: request post parameters, + `application/x-www-form-urlencoded` + and `multipart/form-data` + :param _preload_content: this is a non-applicable field for + the AiohttpClient. + :param _request_timeout: timeout setting for this request. If one + number provided, it will be total request + timeout. It can also be a pair (tuple) of + (connection, read) timeouts. + """ + method = method.upper() + assert method in ['GET', 'HEAD', 'DELETE', 'POST', 'PUT', + 'PATCH', 'OPTIONS'] + + if post_params and body: + raise ValueError( + "body parameter cannot be used with post_params parameter." + ) + + request = httpclient.HTTPRequest(url) + request.allow_nonstandard_methods = True + request.ca_certs = self.ca_certs + request.client_key = self.client_key + request.client_cert = self.client_cert + request.proxy_host = self.proxy_host + request.proxy_port = self.proxy_port + request.method = method + if headers: + request.headers = headers + if 'Content-Type' not in headers: + request.headers['Content-Type'] = 'application/json' + request.request_timeout = _request_timeout or 5 * 60 + + post_params = post_params or {} + + if query_params: + request.url += '?' + urlencode(query_params) + + # For `POST`, `PUT`, `PATCH`, `OPTIONS`, `DELETE` + if method in ['POST', 'PUT', 'PATCH', 'OPTIONS', 'DELETE']: + if re.search('json', headers['Content-Type'], re.IGNORECASE): + if body: + body = json.dumps(body) + request.body = body + elif headers['Content-Type'] == 'application/x-www-form-urlencoded': # noqa: E501 + request.body = urlencode(post_params) + elif headers['Content-Type'] == 'multipart/form-data': + multipart = encode_multipart_formdata(post_params) + request.body, headers['Content-Type'] = multipart + # Pass a `bytes` parameter directly in the body to support + # other content types than Json when `body` argument is provided + # in serialized form + elif isinstance(body, bytes): + request.body = body + else: + # Cannot generate the request from given parameters + msg = """Cannot prepare a request message for provided + arguments. Please check that your arguments match + declared content type.""" + raise ApiException(status=0, reason=msg) + + r = yield self.pool_manager.fetch(request, raise_error=False) + + if _preload_content: + + r = RESTResponse(r) + + # log response body + logger.debug("response body: %s", r.data) + + if not 200 <= r.status <= 299: + raise ApiException(http_resp=r) + + raise tornado.gen.Return(r) + + @tornado.gen.coroutine + def GET(self, url, headers=None, query_params=None, _preload_content=True, + _request_timeout=None): + result = yield self.request("GET", url, + headers=headers, + _preload_content=_preload_content, + _request_timeout=_request_timeout, + query_params=query_params) + raise tornado.gen.Return(result) + + @tornado.gen.coroutine + def HEAD(self, url, headers=None, query_params=None, _preload_content=True, + _request_timeout=None): + result = yield self.request("HEAD", url, + headers=headers, + _preload_content=_preload_content, + _request_timeout=_request_timeout, + query_params=query_params) + raise tornado.gen.Return(result) + + @tornado.gen.coroutine + def OPTIONS(self, url, headers=None, query_params=None, post_params=None, + body=None, _preload_content=True, _request_timeout=None): + result = yield self.request("OPTIONS", url, + headers=headers, + query_params=query_params, + post_params=post_params, + _preload_content=_preload_content, + _request_timeout=_request_timeout, + body=body) + raise tornado.gen.Return(result) + + @tornado.gen.coroutine + def DELETE(self, url, headers=None, query_params=None, body=None, + _preload_content=True, _request_timeout=None): + result = yield self.request("DELETE", url, + headers=headers, + query_params=query_params, + _preload_content=_preload_content, + _request_timeout=_request_timeout, + body=body) + raise tornado.gen.Return(result) + + @tornado.gen.coroutine + def POST(self, url, headers=None, query_params=None, post_params=None, + body=None, _preload_content=True, _request_timeout=None): + result = yield self.request("POST", url, + headers=headers, + query_params=query_params, + post_params=post_params, + _preload_content=_preload_content, + _request_timeout=_request_timeout, + body=body) + raise tornado.gen.Return(result) + + @tornado.gen.coroutine + def PUT(self, url, headers=None, query_params=None, post_params=None, + body=None, _preload_content=True, _request_timeout=None): + result = yield self.request("PUT", url, + headers=headers, + query_params=query_params, + post_params=post_params, + _preload_content=_preload_content, + _request_timeout=_request_timeout, + body=body) + raise tornado.gen.Return(result) + + @tornado.gen.coroutine + def PATCH(self, url, headers=None, query_params=None, post_params=None, + body=None, _preload_content=True, _request_timeout=None): + result = yield self.request("PATCH", url, + headers=headers, + query_params=query_params, + post_params=post_params, + _preload_content=_preload_content, + _request_timeout=_request_timeout, + body=body) + raise tornado.gen.Return(result) + + +class ApiException(Exception): + + def __init__(self, status=None, reason=None, http_resp=None): + if http_resp: + self.status = http_resp.status + self.reason = http_resp.reason + self.body = http_resp.data + self.headers = http_resp.getheaders() + else: + self.status = status + self.reason = reason + self.body = None + self.headers = None + + def __str__(self): + """Custom error messages for exception""" + error_message = "({0})\nReason: {1}\n".format( + self.status, self.reason) + if self.headers: + error_message += "HTTP response headers: {0}\n".format( + self.headers) + + if self.body: + error_message += "HTTP response body: {0}\n".format(self.body) + + return error_message diff --git a/src/main/resources/handlebars/python/tox.mustache b/src/main/resources/handlebars/python/tox.mustache new file mode 100644 index 0000000000..63d12fdeae --- /dev/null +++ b/src/main/resources/handlebars/python/tox.mustache @@ -0,0 +1,20 @@ +[tox] +{{^asyncio}} +envlist = py27, py3 +{{/asyncio}} +{{#asyncio}} +envlist = py3 +{{/asyncio}} + +[testenv] +deps=-r{toxinidir}/requirements.txt + -r{toxinidir}/test-requirements.txt + +commands= +{{^asyncio}} + nosetests \ + [] +{{/asyncio}} +{{#asyncio}} + pytest -v --cov petstore_api +{{/asyncio}} diff --git a/src/main/resources/handlebars/python/travis.mustache b/src/main/resources/handlebars/python/travis.mustache new file mode 100644 index 0000000000..86211e2d4a --- /dev/null +++ b/src/main/resources/handlebars/python/travis.mustache @@ -0,0 +1,14 @@ +# ref: https://docs.travis-ci.com/user/languages/python +language: python +python: + - "2.7" + - "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/python/README.mustache b/src/main/resources/mustache/python/README.mustache new file mode 100644 index 0000000000..094a6474c3 --- /dev/null +++ b/src/main/resources/mustache/python/README.mustache @@ -0,0 +1,129 @@ +# {{{projectName}}} +{{#appDescription}} +{{{appDescription}}} +{{/appDescription}} + +This Python package is automatically generated by the [Swagger Codegen](https://github.com/swagger-api/swagger-codegen) project: + +- API version: {{appVersion}} +- Package version: {{packageVersion}} +{{^hideGenerationTimestamp}} +- Build date: {{generatedDate}} +{{/hideGenerationTimestamp}} +- Build package: {{generatorClass}} +{{#infoUrl}} +For more information, please visit [{{{infoUrl}}}]({{{infoUrl}}}) +{{/infoUrl}} + +## Requirements. + +Python 2.7 and 3.4+ + +## Installation & Usage +### pip install + +If the python package is hosted on Github, you can install directly from Github + +```sh +pip install git+https://github.com/{{{gitUserId}}}/{{{gitRepoId}}}.git +``` +(you may need to run `pip` with root permission: `sudo pip install git+https://github.com/{{{gitUserId}}}/{{{gitRepoId}}}.git`) + +Then import the package: +```python +import {{{packageName}}} +``` + +### Setuptools + +Install via [Setuptools](http://pypi.python.org/pypi/setuptools). + +```sh +python setup.py install --user +``` +(or `sudo python setup.py install` to install the package for all users) + +Then import the package: +```python +import {{{packageName}}} +``` + +## Getting Started + +Please follow the [installation procedure](#installation--usage) and then run the following: + +```python +from __future__ import print_function +import time +import {{{packageName}}} +from {{{packageName}}}.rest import ApiException +from pprint import pprint +{{#apiInfo}}{{#apis}}{{#-first}}{{#operations}}{{#operation}}{{#contents}}{{#-first}}{{#hasAuthMethods}}{{#authMethods}}{{#isBasic}} +# Configure HTTP basic authorization: {{{name}}} +configuration = {{{packageName}}}.Configuration() +configuration.username = 'YOUR_USERNAME' +configuration.password = 'YOUR_PASSWORD'{{/isBasic}}{{#isApiKey}} +# Configure API key authorization: {{{name}}} +configuration = {{{packageName}}}.Configuration() +configuration.api_key['{{{keyParamName}}}'] = 'YOUR_API_KEY' +# Uncomment below to setup prefix (e.g. Bearer) for API key, if needed +# configuration.api_key_prefix['{{{keyParamName}}}'] = 'Bearer'{{/isApiKey}}{{#isOAuth}} +# Configure OAuth2 access token for authorization: {{{name}}} +configuration = {{{packageName}}}.Configuration() +configuration.access_token = 'YOUR_ACCESS_TOKEN'{{/isOAuth}}{{/authMethods}} +{{/hasAuthMethods}} + +# create an instance of the API class +api_instance = {{{packageName}}}.{{{classname}}}({{{packageName}}}.ApiClient(configuration)) +{{#parameters}}{{paramName}} = {{{example}}} # {{{dataType}}} | {{{description}}}{{^required}} (optional){{/required}}{{#defaultValue}} (default to {{{.}}}){{/defaultValue}} +{{/parameters}} + +try: +{{#summary}} # {{{.}}} +{{/summary}} {{#returnType}}api_response = {{/returnType}}api_instance.{{{operationId}}}({{#parameters}}{{#required}}{{paramName}}{{/required}}{{^required}}{{paramName}}={{paramName}}{{/required}}{{#hasMore}}, {{/hasMore}}{{/parameters}}){{#returnType}} + pprint(api_response){{/returnType}} +except ApiException as e: + print("Exception when calling {{classname}}->{{operationId}}: %s\n" % e) +{{/-first}}{{/contents}}{{/operation}}{{/operations}}{{/-first}}{{/apis}}{{/apiInfo}} +``` + +## Documentation for API Endpoints + +All URIs are relative to *{{basePath}}* + +Class | Method | HTTP request | Description +------------ | ------------- | ------------- | ------------- +{{#apiInfo}}{{#apis}}{{#operations}}{{#operation}}*{{classname}}* | [**{{operationId}}**]({{apiDocPath}}{{classname}}.md#{{operationIdLowerCase}}) | **{{httpMethod}}** {{path}} | {{#summary}}{{summary}}{{/summary}} +{{/operation}}{{/operations}}{{/apis}}{{/apiInfo}} + +## Documentation For Models + +{{#models}}{{#model}} - [{{{classname}}}]({{modelDocPath}}{{{classname}}}.md) +{{/model}}{{/models}} + +## Documentation For Authorization + +{{^authMethods}} All endpoints do not require authorization. +{{/authMethods}}{{#authMethods}}{{#last}} Authentication schemes defined for the API:{{/last}}{{/authMethods}} +{{#authMethods}}## {{{name}}} + +{{#isApiKey}}- **Type**: API key +- **API key parameter name**: {{{keyParamName}}} +- **Location**: {{#isKeyInQuery}}URL query string{{/isKeyInQuery}}{{#isKeyInHeader}}HTTP header{{/isKeyInHeader}} +{{/isApiKey}} +{{#isBasic}}- **Type**: HTTP basic authentication +{{/isBasic}} +{{#isOAuth}}- **Type**: OAuth +- **Flow**: {{{flow}}} +- **Authorization URL**: {{{authorizationUrl}}} +- **Scopes**: {{^scopes}}N/A{{/scopes}} +{{#scopes}} - **{{{scope}}}**: {{{description}}} +{{/scopes}} +{{/isOAuth}} + +{{/authMethods}} + +## Author + +{{#apiInfo}}{{#apis}}{{^hasMore}}{{infoEmail}} +{{/hasMore}}{{/apis}}{{/apiInfo}} diff --git a/src/main/resources/mustache/python/__init__api.mustache b/src/main/resources/mustache/python/__init__api.mustache new file mode 100644 index 0000000000..db658a10fa --- /dev/null +++ b/src/main/resources/mustache/python/__init__api.mustache @@ -0,0 +1,7 @@ +from __future__ import absolute_import + +# flake8: noqa + +# import apis into api package +{{#apiInfo}}{{#apis}}from {{apiPackage}}.{{classVarName}} import {{classname}} +{{/apis}}{{/apiInfo}} \ No newline at end of file diff --git a/src/main/resources/mustache/python/__init__model.mustache b/src/main/resources/mustache/python/__init__model.mustache new file mode 100644 index 0000000000..2266b3d17f --- /dev/null +++ b/src/main/resources/mustache/python/__init__model.mustache @@ -0,0 +1,10 @@ +# coding: utf-8 + +# flake8: noqa +{{>partial_header}} + +from __future__ import absolute_import + +# import models into model package +{{#models}}{{#model}}from {{modelPackage}}.{{classFilename}} import {{classname}}{{/model}} +{{/models}} diff --git a/src/main/resources/mustache/python/__init__package.mustache b/src/main/resources/mustache/python/__init__package.mustache new file mode 100644 index 0000000000..cd42506f54 --- /dev/null +++ b/src/main/resources/mustache/python/__init__package.mustache @@ -0,0 +1,17 @@ +# coding: utf-8 + +# flake8: noqa + +{{>partial_header}} + +from __future__ import absolute_import + +# import apis into sdk package +{{#apiInfo}}{{#apis}}from {{apiPackage}}.{{classVarName}} import {{classname}} +{{/apis}}{{/apiInfo}} +# import ApiClient +from {{packageName}}.api_client import ApiClient +from {{packageName}}.configuration import Configuration +# import models into sdk package +{{#models}}{{#model}}from {{modelPackage}}.{{classFilename}} import {{classname}} +{{/model}}{{/models}} \ No newline at end of file diff --git a/src/main/resources/mustache/python/__init__test.mustache b/src/main/resources/mustache/python/__init__test.mustache new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/main/resources/mustache/python/api.mustache b/src/main/resources/mustache/python/api.mustache new file mode 100644 index 0000000000..8e22ab317e --- /dev/null +++ b/src/main/resources/mustache/python/api.mustache @@ -0,0 +1,216 @@ +# coding: utf-8 + +{{>partial_header}} + +from __future__ import absolute_import + +import re # noqa: F401 + +# python 2 and python 3 compatibility library +import six + +from {{packageName}}.api_client import ApiClient + + +{{#operations}} +class {{classname}}(object): + """NOTE: This class is auto generated by the swagger code generator program. + + Do not edit the class manually. + Ref: https://github.com/swagger-api/swagger-codegen + """ + + def __init__(self, api_client=None): + if api_client is None: + api_client = ApiClient() + self.api_client = api_client +{{#operation}} +{{#contents}} + + def {{operationId}}(self, {{#sortParamsByRequiredFlag}}{{#parameters}}{{#required}}{{paramName}}, {{/required}}{{/parameters}}{{/sortParamsByRequiredFlag}}**kwargs): # noqa: E501 + """{{#summary}}{{{.}}}{{/summary}}{{^summary}}{{operationId}}{{/summary}} # noqa: E501 + +{{#notes}} + {{{notes}}} # noqa: E501 +{{/notes}} + This method makes a synchronous HTTP request by default. To make an + asynchronous HTTP request, please pass async_req=True +{{#sortParamsByRequiredFlag}} + >>> thread = api.{{operationId}}({{#parameters}}{{#required}}{{paramName}}, {{/required}}{{/parameters}}async_req=True) +{{/sortParamsByRequiredFlag}} +{{^sortParamsByRequiredFlag}} + >>> thread = api.{{operationId}}({{#parameters}}{{#required}}{{paramName}}={{paramName}}_value, {{/required}}{{/parameters}}async_req=True) +{{/sortParamsByRequiredFlag}} + >>> result = thread.get() + + :param async_req bool +{{#parameters}} + :param {{dataType}} {{paramName}}:{{#description}} {{{description}}}{{/description}}{{#required}} (required){{/required}}{{#optional}}(optional){{/optional}} +{{/parameters}} + :return: {{#returnType}}{{returnType}}{{/returnType}}{{^returnType}}None{{/returnType}} + If the method is called asynchronously, + returns the request thread. + """ + kwargs['_return_http_data_only'] = True + if kwargs.get('async_req'): + return self.{{operationId}}_with_http_info({{#sortParamsByRequiredFlag}}{{#parameters}}{{#required}}{{paramName}}, {{/required}}{{/parameters}}{{/sortParamsByRequiredFlag}}**kwargs) # noqa: E501 + else: + (data) = self.{{operationId}}_with_http_info({{#sortParamsByRequiredFlag}}{{#parameters}}{{#required}}{{paramName}}, {{/required}}{{/parameters}}{{/sortParamsByRequiredFlag}}**kwargs) # noqa: E501 + return data + + def {{operationId}}_with_http_info(self, {{#sortParamsByRequiredFlag}}{{#parameters}}{{#required}}{{paramName}}, {{/required}}{{/parameters}}{{/sortParamsByRequiredFlag}}**kwargs): # noqa: E501 + """{{#summary}}{{{.}}}{{/summary}}{{^summary}}{{operationId}}{{/summary}} # noqa: E501 + +{{#notes}} + {{{notes}}} # noqa: E501 +{{/notes}} + This method makes a synchronous HTTP request by default. To make an + asynchronous HTTP request, please pass async_req=True +{{#sortParamsByRequiredFlag}} + >>> thread = api.{{operationId}}_with_http_info({{#parameters}}{{#required}}{{paramName}}, {{/required}}{{/parameters}}async_req=True) +{{/sortParamsByRequiredFlag}} +{{^sortParamsByRequiredFlag}} + >>> thread = api.{{operationId}}_with_http_info({{#parameters}}{{#required}}{{paramName}}={{paramName}}_value, {{/required}}{{/parameters}}async_req=True) +{{/sortParamsByRequiredFlag}} + >>> result = thread.get() + + :param async_req bool +{{#parameters}} + :param {{dataType}} {{paramName}}:{{#description}} {{{description}}}{{/description}}{{#required}} (required){{/required}}{{#optional}}(optional{{#defaultValue}}, default to {{{.}}}{{/defaultValue}}){{/optional}} +{{/parameters}} + :return: {{#returnType}}{{returnType}}{{/returnType}}{{^returnType}}None{{/returnType}} + If the method is called asynchronously, + returns the request thread. + """ + + all_params = [{{#parameters}}'{{paramName}}'{{#hasMore}}, {{/hasMore}}{{/parameters}}] # noqa: E501 + all_params.append('async_req') + all_params.append('_return_http_data_only') + all_params.append('_preload_content') + all_params.append('_request_timeout') + + params = locals() + for key, val in six.iteritems(params['kwargs']): + if key not in all_params: + raise TypeError( + "Got an unexpected keyword argument '%s'" + " to method {{operationId}}" % key + ) + params[key] = val + del params['kwargs'] +{{#parameters}} +{{#required}} + # verify the required parameter '{{paramName}}' is set + if ('{{paramName}}' not in params or + params['{{paramName}}'] is None): + raise ValueError("Missing the required parameter `{{paramName}}` when calling `{{operationId}}`") # noqa: E501 +{{/required}} +{{/parameters}} + +{{#parameters}} +{{#hasValidation}} + {{#maxLength}} + if ('{{paramName}}' in params and + len(params['{{paramName}}']) > {{maxLength}}): + raise ValueError("Invalid value for parameter `{{paramName}}` when calling `{{operationId}}`, length must be less than or equal to `{{maxLength}}`") # noqa: E501 + {{/maxLength}} + {{#minLength}} + if ('{{paramName}}' in params and + len(params['{{paramName}}']) < {{minLength}}): + raise ValueError("Invalid value for parameter `{{paramName}}` when calling `{{operationId}}`, length must be greater than or equal to `{{minLength}}`") # noqa: E501 + {{/minLength}} + {{#maximum}} + if '{{paramName}}' in params and params['{{paramName}}'] >{{#exclusiveMaximum}}={{/exclusiveMaximum}} {{maximum}}: # noqa: E501 + raise ValueError("Invalid value for parameter `{{paramName}}` when calling `{{operationId}}`, must be a value less than {{^exclusiveMaximum}}or equal to {{/exclusiveMaximum}}`{{maximum}}`") # noqa: E501 + {{/maximum}} + {{#minimum}} + if '{{paramName}}' in params and params['{{paramName}}'] <{{#exclusiveMinimum}}={{/exclusiveMinimum}} {{minimum}}: # noqa: E501 + raise ValueError("Invalid value for parameter `{{paramName}}` when calling `{{operationId}}`, must be a value greater than {{^exclusiveMinimum}}or equal to {{/exclusiveMinimum}}`{{minimum}}`") # noqa: E501 + {{/minimum}} + {{#pattern}} + if '{{paramName}}' in params and not re.search(r'{{{vendorExtensions.x-regex}}}', params['{{paramName}}']{{#vendorExtensions.x-modifiers}}{{#-first}}, flags={{/-first}}re.{{.}}{{^-last}} | {{/-last}}{{/vendorExtensions.x-modifiers}}): # noqa: E501 + raise ValueError("Invalid value for parameter `{{paramName}}` when calling `{{operationId}}`, must conform to the pattern `{{{pattern}}}`") # noqa: E501 + {{/pattern}} + {{#maxItems}} + if ('{{paramName}}' in params and + len(params['{{paramName}}']) > {{maxItems}}): + raise ValueError("Invalid value for parameter `{{paramName}}` when calling `{{operationId}}`, number of items must be less than or equal to `{{maxItems}}`") # noqa: E501 + {{/maxItems}} + {{#minItems}} + if ('{{paramName}}' in params and + len(params['{{paramName}}']) < {{minItems}}): + raise ValueError("Invalid value for parameter `{{paramName}}` when calling `{{operationId}}`, number of items must be greater than or equal to `{{minItems}}`") # noqa: E501 + {{/minItems}} +{{/hasValidation}} +{{#-last}} +{{/-last}} +{{/parameters}} + collection_formats = {} + + path_params = {} +{{#pathParams}} + if '{{paramName}}' in params: + path_params['{{baseName}}'] = params['{{paramName}}']{{#isListContainer}} # noqa: E501 + collection_formats['{{baseName}}'] = '{{collectionFormat}}'{{/isListContainer}} # noqa: E501 +{{/pathParams}} + + query_params = [] +{{#queryParams}} + if '{{paramName}}' in params: + query_params.append(('{{baseName}}', params['{{paramName}}'])){{#isListContainer}} # noqa: E501 + collection_formats['{{baseName}}'] = '{{collectionFormat}}'{{/isListContainer}} # noqa: E501 +{{/queryParams}} + + header_params = {} +{{#headerParams}} + if '{{paramName}}' in params: + header_params['{{baseName}}'] = params['{{paramName}}']{{#isListContainer}} # noqa: E501 + collection_formats['{{baseName}}'] = '{{collectionFormat}}'{{/isListContainer}} # noqa: E501 +{{/headerParams}} + + form_params = [] + local_var_files = {} +{{#formParams}} + if '{{paramName}}' in params: + {{#notFile}}form_params.append(('{{baseName}}', params['{{paramName}}'])){{/notFile}}{{#isFile}}local_var_files['{{baseName}}'] = params['{{paramName}}']{{/isFile}}{{#isListContainer}} # noqa: E501 + collection_formats['{{baseName}}'] = '{{collectionFormat}}'{{/isListContainer}} # noqa: E501 +{{/formParams}} + + body_params = None +{{#bodyParam}} + if '{{paramName}}' in params: + body_params = params['{{paramName}}'] +{{/bodyParam}} + {{#hasProduces}} + # HTTP header `Accept` + header_params['Accept'] = self.api_client.select_header_accept( + [{{#produces}}'{{{mediaType}}}'{{#hasMore}}, {{/hasMore}}{{/produces}}]) # noqa: E501 + + {{/hasProduces}} + {{#hasConsumes}} + # HTTP header `Content-Type` + header_params['Content-Type'] = self.api_client.select_header_content_type( # noqa: E501 + [{{#consumes}}'{{{mediaType}}}'{{#hasMore}}, {{/hasMore}}{{/consumes}}]) # noqa: E501 + + {{/hasConsumes}} + # Authentication setting + auth_settings = [{{#authMethods}}'{{name}}'{{#hasMore}}, {{/hasMore}}{{/authMethods}}] # noqa: E501 + + return self.api_client.call_api( + '{{{path}}}', '{{httpMethod}}', + path_params, + query_params, + header_params, + body=body_params, + post_params=form_params, + files=local_var_files, + response_type={{#returnType}}'{{returnType}}'{{/returnType}}{{^returnType}}None{{/returnType}}, # noqa: E501 + auth_settings=auth_settings, + async_req=params.get('async_req'), + _return_http_data_only=params.get('_return_http_data_only'), + _preload_content=params.get('_preload_content', True), + _request_timeout=params.get('_request_timeout'), + collection_formats=collection_formats) +{{/contents}} +{{/operation}} +{{/operations}} diff --git a/src/main/resources/mustache/python/api_client.mustache b/src/main/resources/mustache/python/api_client.mustache new file mode 100644 index 0000000000..e46230fd75 --- /dev/null +++ b/src/main/resources/mustache/python/api_client.mustache @@ -0,0 +1,635 @@ +# coding: utf-8 +{{>partial_header}} +from __future__ import absolute_import + +import datetime +import json +import mimetypes +from multiprocessing.pool import ThreadPool +import os +import re +import tempfile + +# python 2 and python 3 compatibility library +import six +from six.moves.urllib.parse import quote +{{#tornado}} +import tornado.gen +{{/tornado}} + +from {{packageName}}.configuration import Configuration +import {{modelPackage}} +from {{packageName}} import rest + + +class ApiClient(object): + """Generic API client for Swagger client library builds. + + Swagger generic API client. This client handles the client- + server communication, and is invariant across implementations. Specifics of + the methods and models for each application are generated from the Swagger + templates. + + NOTE: This class is auto generated by the swagger code generator program. + Ref: https://github.com/swagger-api/swagger-codegen + Do not edit the class manually. + + :param configuration: .Configuration object for this client + :param header_name: a header to pass when making calls to the API. + :param header_value: a header value to pass when making calls to + the API. + :param cookie: a cookie to include in the header when making calls + to the API + """ + + PRIMITIVE_TYPES = (float, bool, bytes, six.text_type) + six.integer_types + NATIVE_TYPES_MAPPING = { + 'int': int, + 'long': int if six.PY3 else long, # noqa: F821 + 'float': float, + 'str': str, + 'bool': bool, + 'date': datetime.date, + 'datetime': datetime.datetime, + 'object': object, + } + + def __init__(self, configuration=None, header_name=None, header_value=None, + cookie=None): + if configuration is None: + configuration = Configuration() + self.configuration = configuration + + self.pool = ThreadPool() + self.rest_client = rest.RESTClientObject(configuration) + self.default_headers = {} + if header_name is not None: + self.default_headers[header_name] = header_value + self.cookie = cookie + # Set default User-Agent. + self.user_agent = '{{#httpUserAgent}}{{{.}}}{{/httpUserAgent}}{{^httpUserAgent}}Swagger-Codegen/{{{packageVersion}}}/python{{/httpUserAgent}}' + + def __del__(self): + self.pool.close() + self.pool.join() + + @property + def user_agent(self): + """User agent for this API client""" + return self.default_headers['User-Agent'] + + @user_agent.setter + def user_agent(self, value): + self.default_headers['User-Agent'] = value + + def set_default_header(self, header_name, header_value): + self.default_headers[header_name] = header_value + + {{#tornado}} + @tornado.gen.coroutine + {{/tornado}} + {{#asyncio}}async {{/asyncio}}def __call_api( + self, resource_path, method, path_params=None, + query_params=None, header_params=None, body=None, post_params=None, + files=None, response_type=None, auth_settings=None, + _return_http_data_only=None, collection_formats=None, + _preload_content=True, _request_timeout=None): + + config = self.configuration + + # header parameters + header_params = header_params or {} + header_params.update(self.default_headers) + if self.cookie: + header_params['Cookie'] = self.cookie + if header_params: + header_params = self.sanitize_for_serialization(header_params) + header_params = dict(self.parameters_to_tuples(header_params, + collection_formats)) + + # path parameters + if path_params: + path_params = self.sanitize_for_serialization(path_params) + path_params = self.parameters_to_tuples(path_params, + collection_formats) + for k, v in path_params: + # specified safe chars, encode everything + resource_path = resource_path.replace( + '{%s}' % k, + quote(str(v), safe=config.safe_chars_for_path_param) + ) + + # query parameters + if query_params: + query_params = self.sanitize_for_serialization(query_params) + query_params = self.parameters_to_tuples(query_params, + collection_formats) + + # post parameters + if post_params or files: + post_params = self.prepare_post_parameters(post_params, files) + post_params = self.sanitize_for_serialization(post_params) + post_params = self.parameters_to_tuples(post_params, + collection_formats) + + # auth setting + self.update_params_for_auth(header_params, query_params, auth_settings) + + # body + if body: + body = self.sanitize_for_serialization(body) + + # request url + url = self.configuration.host + resource_path + + # perform request and return response + response_data = {{#asyncio}}await {{/asyncio}}{{#tornado}}yield {{/tornado}}self.request( + method, url, query_params=query_params, headers=header_params, + post_params=post_params, body=body, + _preload_content=_preload_content, + _request_timeout=_request_timeout) + + self.last_response = response_data + + return_data = response_data + if _preload_content: + # deserialize response data + if response_type: + return_data = self.deserialize(response_data, response_type) + else: + return_data = None + +{{^tornado}} + if _return_http_data_only: + return (return_data) + else: + return (return_data, response_data.status, + response_data.getheaders()) +{{/tornado}} +{{#tornado}} + if _return_http_data_only: + raise tornado.gen.Return(return_data) + else: + raise tornado.gen.Return((return_data, response_data.status, + response_data.getheaders())) +{{/tornado}} + + def sanitize_for_serialization(self, obj): + """Builds a JSON POST object. + + If obj is None, return None. + If obj is str, int, long, float, bool, return directly. + If obj is datetime.datetime, datetime.date + convert to string in iso8601 format. + If obj is list, sanitize each element in the list. + If obj is dict, return the dict. + If obj is swagger model, return the properties dict. + + :param obj: The data to serialize. + :return: The serialized form of data. + """ + if obj is None: + return None + elif isinstance(obj, self.PRIMITIVE_TYPES): + return obj + elif isinstance(obj, list): + return [self.sanitize_for_serialization(sub_obj) + for sub_obj in obj] + elif isinstance(obj, tuple): + return tuple(self.sanitize_for_serialization(sub_obj) + for sub_obj in obj) + elif isinstance(obj, (datetime.datetime, datetime.date)): + return obj.isoformat() + + if isinstance(obj, dict): + obj_dict = obj + else: + # Convert model obj to dict except + # attributes `swagger_types`, `attribute_map` + # and attributes which value is not None. + # Convert attribute name to json key in + # model definition for request. + obj_dict = {obj.attribute_map[attr]: getattr(obj, attr) + for attr, _ in six.iteritems(obj.swagger_types) + if getattr(obj, attr) is not None} + + return {key: self.sanitize_for_serialization(val) + for key, val in six.iteritems(obj_dict)} + + def deserialize(self, response, response_type): + """Deserializes response into an object. + + :param response: RESTResponse object to be deserialized. + :param response_type: class literal for + deserialized object, or string of class name. + + :return: deserialized object. + """ + # handle file downloading + # save response body into a tmp file and return the instance + if response_type == "file": + return self.__deserialize_file(response) + + # fetch data from response object + try: + data = json.loads(response.data) + except ValueError: + data = response.data + + return self.__deserialize(data, response_type) + + def __deserialize(self, 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 type(klass) == str: + if klass.startswith('list['): + sub_kls = re.match(r'list\[(.*)\]', klass).group(1) + return [self.__deserialize(sub_data, sub_kls) + for sub_data in data] + + if klass.startswith('dict('): + sub_kls = re.match(r'dict\(([^,]*), (.*)\)', klass).group(2) + return {k: self.__deserialize(v, sub_kls) + for k, v in six.iteritems(data)} + + # convert str to class + if klass in self.NATIVE_TYPES_MAPPING: + klass = self.NATIVE_TYPES_MAPPING[klass] + else: + klass = getattr({{modelPackage}}, klass) + + if klass in self.PRIMITIVE_TYPES: + return self.__deserialize_primitive(data, klass) + elif klass == object: + return self.__deserialize_object(data) + elif klass == datetime.date: + return self.__deserialize_date(data) + elif klass == datetime.datetime: + return self.__deserialize_datatime(data) + else: + return self.__deserialize_model(data, klass) + + def call_api(self, resource_path, method, + path_params=None, query_params=None, header_params=None, + body=None, post_params=None, files=None, + response_type=None, auth_settings=None, async_req=None, + _return_http_data_only=None, collection_formats=None, + _preload_content=True, _request_timeout=None): + """Makes the HTTP request (synchronous) and returns deserialized data. + + To make an async request, set the async_req parameter. + + :param resource_path: Path to method endpoint. + :param method: Method to call. + :param path_params: Path parameters in the url. + :param query_params: Query parameters in the url. + :param header_params: Header parameters to be + placed in the request header. + :param body: Request body. + :param post_params dict: Request post form parameters, + for `application/x-www-form-urlencoded`, `multipart/form-data`. + :param auth_settings list: Auth Settings names for the request. + :param response: Response data type. + :param files dict: key -> filename, value -> filepath, + for `multipart/form-data`. + :param async_req bool: execute request asynchronously + :param _return_http_data_only: response data without head status code + and headers + :param collection_formats: dict of collection formats for path, query, + header, and post parameters. + :param _preload_content: if False, the urllib3.HTTPResponse object will + be returned without reading/decoding response + data. Default is True. + :param _request_timeout: timeout setting for this request. If one + number provided, it will be total request + timeout. It can also be a pair (tuple) of + (connection, read) timeouts. + :return: + If async_req parameter is True, + the request will be called asynchronously. + The method will return the request thread. + If parameter async_req is False or missing, + then the method will return the response directly. + """ + if not async_req: + return self.__call_api(resource_path, method, + path_params, query_params, header_params, + body, post_params, files, + response_type, auth_settings, + _return_http_data_only, collection_formats, + _preload_content, _request_timeout) + else: + thread = self.pool.apply_async(self.__call_api, (resource_path, + method, path_params, query_params, + header_params, body, + post_params, files, + response_type, auth_settings, + _return_http_data_only, + collection_formats, + _preload_content, _request_timeout)) + return thread + + def request(self, method, url, query_params=None, headers=None, + post_params=None, body=None, _preload_content=True, + _request_timeout=None): + """Makes the HTTP request using RESTClient.""" + if method == "GET": + return self.rest_client.GET(url, + query_params=query_params, + _preload_content=_preload_content, + _request_timeout=_request_timeout, + headers=headers) + elif method == "HEAD": + return self.rest_client.HEAD(url, + query_params=query_params, + _preload_content=_preload_content, + _request_timeout=_request_timeout, + headers=headers) + elif method == "OPTIONS": + return self.rest_client.OPTIONS(url, + query_params=query_params, + headers=headers, + post_params=post_params, + _preload_content=_preload_content, + _request_timeout=_request_timeout, + body=body) + elif method == "POST": + return self.rest_client.POST(url, + query_params=query_params, + headers=headers, + post_params=post_params, + _preload_content=_preload_content, + _request_timeout=_request_timeout, + body=body) + elif method == "PUT": + return self.rest_client.PUT(url, + query_params=query_params, + headers=headers, + post_params=post_params, + _preload_content=_preload_content, + _request_timeout=_request_timeout, + body=body) + elif method == "PATCH": + return self.rest_client.PATCH(url, + query_params=query_params, + headers=headers, + post_params=post_params, + _preload_content=_preload_content, + _request_timeout=_request_timeout, + body=body) + elif method == "DELETE": + return self.rest_client.DELETE(url, + query_params=query_params, + headers=headers, + _preload_content=_preload_content, + _request_timeout=_request_timeout, + body=body) + else: + raise ValueError( + "http method must be `GET`, `HEAD`, `OPTIONS`," + " `POST`, `PATCH`, `PUT` or `DELETE`." + ) + + def parameters_to_tuples(self, params, collection_formats): + """Get parameters as list of tuples, formatting collections. + + :param params: Parameters as dict or list of two-tuples + :param dict collection_formats: Parameter collection formats + :return: Parameters as list of tuples, collections formatted + """ + new_params = [] + if collection_formats is None: + collection_formats = {} + for k, v in six.iteritems(params) if isinstance(params, dict) else params: # noqa: E501 + if k in collection_formats: + collection_format = collection_formats[k] + if collection_format == 'multi': + new_params.extend((k, value) for value in v) + else: + if collection_format == 'ssv': + delimiter = ' ' + elif collection_format == 'tsv': + delimiter = '\t' + elif collection_format == 'pipes': + delimiter = '|' + else: # csv is the default + delimiter = ',' + new_params.append( + (k, delimiter.join(str(value) for value in v))) + else: + new_params.append((k, v)) + return new_params + + def prepare_post_parameters(self, post_params=None, files=None): + """Builds form parameters. + + :param post_params: Normal form parameters. + :param files: File parameters. + :return: Form parameters with files. + """ + params = [] + + if post_params: + params = post_params + + if files: + for k, v in six.iteritems(files): + if not v: + continue + file_names = v if type(v) is list else [v] + for n in file_names: + with open(n, 'rb') as f: + filename = os.path.basename(f.name) + filedata = f.read() + mimetype = (mimetypes.guess_type(filename)[0] or + 'application/octet-stream') + params.append( + tuple([k, tuple([filename, filedata, mimetype])])) + + return params + + def select_header_accept(self, accepts): + """Returns `Accept` based on an array of accepts provided. + + :param accepts: List of headers. + :return: Accept (e.g. application/json). + """ + if not accepts: + return + + accepts = [x.lower() for x in accepts] + + if 'application/json' in accepts: + return 'application/json' + else: + return ', '.join(accepts) + + def select_header_content_type(self, content_types): + """Returns `Content-Type` based on an array of content_types provided. + + :param content_types: List of content-types. + :return: Content-Type (e.g. application/json). + """ + if not content_types: + return 'application/json' + + content_types = [x.lower() for x in content_types] + + if 'application/json' in content_types or '*/*' in content_types: + return 'application/json' + else: + return content_types[0] + + def update_params_for_auth(self, headers, querys, auth_settings): + """Updates header and query params based on authentication setting. + + :param headers: Header parameters dict to be updated. + :param querys: Query parameters tuple list to be updated. + :param auth_settings: Authentication setting identifiers list. + """ + if not auth_settings: + return + + for auth in auth_settings: + auth_setting = self.configuration.auth_settings().get(auth) + if auth_setting: + if not auth_setting['value']: + continue + elif auth_setting['in'] == 'header': + headers[auth_setting['key']] = auth_setting['value'] + elif auth_setting['in'] == 'query': + querys.append((auth_setting['key'], auth_setting['value'])) + else: + raise ValueError( + 'Authentication token must be in `query` or `header`' + ) + + def __deserialize_file(self, response): + """Deserializes body to file + + Saves response body into a file in a temporary folder, + using the filename from the `Content-Disposition` header if provided. + + :param response: RESTResponse. + :return: file path. + """ + fd, path = tempfile.mkstemp(dir=self.configuration.temp_folder_path) + os.close(fd) + os.remove(path) + + content_disposition = response.getheader("Content-Disposition") + if content_disposition: + filename = re.search(r'filename=[\'"]?([^\'"\s]+)[\'"]?', + content_disposition).group(1) + path = os.path.join(os.path.dirname(path), filename) + + with open(path, "wb") as f: + f.write(response.data) + + return path + + def __deserialize_primitive(self, data, klass): + """Deserializes string to primitive type. + + :param data: str. + :param klass: class literal. + + :return: int, long, float, str, bool. + """ + try: + return klass(data) + except UnicodeEncodeError: + return six.text_type(data) + except TypeError: + return data + + def __deserialize_object(self, value): + """Return a original value. + + :return: object. + """ + return value + + def __deserialize_date(self, string): + """Deserializes string to date. + + :param string: str. + :return: date. + """ + try: + from dateutil.parser import parse + return parse(string).date() + except ImportError: + return string + except ValueError: + raise rest.ApiException( + status=0, + reason="Failed to parse `{0}` as date object".format(string) + ) + + def __deserialize_datatime(self, string): + """Deserializes string to datetime. + + The string should be in iso8601 datetime format. + + :param string: str. + :return: datetime. + """ + try: + from dateutil.parser import parse + return parse(string) + except ImportError: + return string + except ValueError: + raise rest.ApiException( + status=0, + reason=( + "Failed to parse `{0}` as datetime object" + .format(string) + ) + ) + + def __hasattr(self, object, name): + return name in object.__class__.__dict__ + + def __deserialize_model(self, data, klass): + """Deserializes list or dict to model. + + :param data: dict, list. + :param klass: class literal. + :return: model object. + """ + + if not klass.swagger_types and not self.__hasattr(klass, 'get_real_child_model'): + return data + + kwargs = {} + if klass.swagger_types is not None: + for attr, attr_type in six.iteritems(klass.swagger_types): + if (data is not None and + klass.attribute_map[attr] in data and + isinstance(data, (list, dict))): + value = data[klass.attribute_map[attr]] + kwargs[attr] = self.__deserialize(value, attr_type) + + instance = klass(**kwargs) + + if (isinstance(instance, dict) and + klass.swagger_types is not None and + isinstance(data, dict)): + for key, value in data.items(): + if key not in klass.swagger_types: + instance[key] = value + if self.__hasattr(instance, 'get_real_child_model'): + klass_name = instance.get_real_child_model(data) + if klass_name: + instance = self.__deserialize(data, klass_name) + return instance diff --git a/src/main/resources/mustache/python/api_doc.mustache b/src/main/resources/mustache/python/api_doc.mustache new file mode 100644 index 0000000000..32c26d929d --- /dev/null +++ b/src/main/resources/mustache/python/api_doc.mustache @@ -0,0 +1,85 @@ +# {{packageName}}.{{classname}}{{#description}} +{{description}}{{/description}} + +All URIs are relative to *{{basePath}}* + +Method | HTTP request | Description +------------- | ------------- | ------------- +{{#operations}}{{#operation}}[**{{operationId}}**]({{classname}}.md#{{operationId}}) | **{{httpMethod}}** {{path}} | {{#summary}}{{summary}}{{/summary}} +{{/operation}}{{/operations}} + +{{#operations}} +{{#operation}} +# **{{{operationId}}}** +> {{#returnType}}{{{returnType}}} {{/returnType}}{{{operationId}}}({{#allParams}}{{#required}}{{{paramName}}}{{/required}}{{^required}}{{{paramName}}}={{{paramName}}}{{/required}}{{#hasMore}}, {{/hasMore}}{{/allParams}}) + +{{{summary}}}{{#notes}} + +{{{notes}}}{{/notes}} + +### Example +```python +from __future__ import print_function +import time +import {{{packageName}}} +from {{{packageName}}}.rest import ApiException +from pprint import pprint +{{#hasAuthMethods}}{{#authMethods}}{{#isBasic}} +# Configure HTTP basic authorization: {{{name}}} +configuration = {{{packageName}}}.Configuration() +configuration.username = 'YOUR_USERNAME' +configuration.password = 'YOUR_PASSWORD'{{/isBasic}}{{#isApiKey}} +# Configure API key authorization: {{{name}}} +configuration = {{{packageName}}}.Configuration() +configuration.api_key['{{{keyParamName}}}'] = 'YOUR_API_KEY' +# Uncomment below to setup prefix (e.g. Bearer) for API key, if needed +# configuration.api_key_prefix['{{{keyParamName}}}'] = 'Bearer'{{/isApiKey}}{{#isOAuth}} +# Configure OAuth2 access token for authorization: {{{name}}} +configuration = {{{packageName}}}.Configuration() +configuration.access_token = 'YOUR_ACCESS_TOKEN'{{/isOAuth}}{{/authMethods}} + +# create an instance of the API class +api_instance = {{{packageName}}}.{{{classname}}}({{{packageName}}}.ApiClient(configuration)) +{{#allParams}}{{paramName}} = {{{example}}} # {{{dataType}}} | {{{description}}}{{^required}} (optional){{/required}}{{#defaultValue}} (default to {{{.}}}){{/defaultValue}} +{{/allParams}} +{{/hasAuthMethods}} +{{^hasAuthMethods}} + +# create an instance of the API class +api_instance = {{{packageName}}}.{{{classname}}}() +{{#allParams}}{{paramName}} = {{{example}}} # {{{dataType}}} | {{{description}}}{{^required}} (optional){{/required}}{{#defaultValue}} (default to {{{.}}}){{/defaultValue}} +{{/allParams}} +{{/hasAuthMethods}} + +try: +{{#summary}} # {{{.}}} +{{/summary}} {{#returnType}}api_response = {{/returnType}}api_instance.{{{operationId}}}({{#allParams}}{{#required}}{{paramName}}{{/required}}{{^required}}{{paramName}}={{paramName}}{{/required}}{{#hasMore}}, {{/hasMore}}{{/allParams}}){{#returnType}} + pprint(api_response){{/returnType}} +except ApiException as e: + print("Exception when calling {{classname}}->{{operationId}}: %s\n" % e) +``` + +### Parameters +{{^allParams}}This endpoint does not need any parameter.{{/allParams}}{{#allParams}}{{#-last}} +Name | Type | Description | Notes +------------- | ------------- | ------------- | -------------{{/-last}}{{/allParams}} +{{#allParams}} **{{paramName}}** | {{#isFile}}**{{dataType}}**{{/isFile}}{{^isFile}}{{#isPrimitiveType}}**{{dataType}}**{{/isPrimitiveType}}{{^isPrimitiveType}}[**{{dataType}}**]({{baseType}}.md){{/isPrimitiveType}}{{/isFile}}| {{description}} | {{^required}}[optional] {{/required}}{{#defaultValue}}[default to {{defaultValue}}]{{/defaultValue}} +{{/allParams}} + +### Return type + +{{#returnType}}{{#returnTypeIsPrimitive}}**{{{returnType}}}**{{/returnTypeIsPrimitive}}{{^returnTypeIsPrimitive}}[**{{{returnType}}}**]({{returnBaseType}}.md){{/returnTypeIsPrimitive}}{{/returnType}}{{^returnType}}void (empty response body){{/returnType}} + +### Authorization + +{{^authMethods}}No authorization required{{/authMethods}}{{#authMethods}}[{{{name}}}](../README.md#{{{name}}}){{^-last}}, {{/-last}}{{/authMethods}} + +### HTTP request headers + + - **Content-Type**: {{#consumes}}{{{mediaType}}}{{#hasMore}}, {{/hasMore}}{{/consumes}}{{^consumes}}Not defined{{/consumes}} + - **Accept**: {{#produces}}{{{mediaType}}}{{#hasMore}}, {{/hasMore}}{{/produces}}{{^produces}}Not defined{{/produces}} + +[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) + +{{/operation}} +{{/operations}} diff --git a/src/main/resources/mustache/python/api_test.mustache b/src/main/resources/mustache/python/api_test.mustache new file mode 100644 index 0000000000..90fafc15f3 --- /dev/null +++ b/src/main/resources/mustache/python/api_test.mustache @@ -0,0 +1,37 @@ +# coding: utf-8 + +{{>partial_header}} + +from __future__ import absolute_import + +import unittest + +import {{packageName}} +from {{apiPackage}}.{{classVarName}} import {{classname}} # noqa: E501 +from {{packageName}}.rest import ApiException + + +class {{#operations}}Test{{classname}}(unittest.TestCase): + """{{classname}} unit test stubs""" + + def setUp(self): + self.api = {{apiPackage}}.{{classVarName}}.{{classname}}() # noqa: E501 + + def tearDown(self): + pass + + {{#operation}} + def test_{{operationId}}(self): + """Test case for {{{operationId}}} + +{{#summary}} + {{{summary}}} # noqa: E501 +{{/summary}} + """ + pass + + {{/operation}} +{{/operations}} + +if __name__ == '__main__': + unittest.main() diff --git a/src/main/resources/mustache/python/asyncio/rest.mustache b/src/main/resources/mustache/python/asyncio/rest.mustache new file mode 100644 index 0000000000..39003abfbf --- /dev/null +++ b/src/main/resources/mustache/python/asyncio/rest.mustache @@ -0,0 +1,260 @@ +# coding: utf-8 + +{{>partial_header}} + +import io +import json +import logging +import re +import ssl + +import aiohttp +import certifi +# python 2 and python 3 compatibility library +from six.moves.urllib.parse import urlencode + +logger = logging.getLogger(__name__) + + +class RESTResponse(io.IOBase): + + def __init__(self, resp, data): + self.aiohttp_response = resp + self.status = resp.status + self.reason = resp.reason + self.data = data + + def getheaders(self): + """Returns a CIMultiDictProxy of the response headers.""" + return self.aiohttp_response.headers + + def getheader(self, name, default=None): + """Returns a given response header.""" + return self.aiohttp_response.headers.get(name, default) + + +class RESTClientObject(object): + + def __init__(self, configuration, pools_size=4, maxsize=4): + # maxsize is number of requests to host that are allowed in parallel + # ca_certs vs cert_file vs key_file + # http://stackoverflow.com/a/23957365/2985775 + + # ca_certs + if configuration.ssl_ca_cert: + ca_certs = configuration.ssl_ca_cert + else: + # if not set certificate file, use Mozilla's root certificates. + ca_certs = certifi.where() + + ssl_context = ssl.create_default_context(cafile=ca_certs) + if configuration.cert_file: + ssl_context.load_cert_chain( + configuration.cert_file, keyfile=configuration.key_file + ) + + connector = aiohttp.TCPConnector( + limit=maxsize, + ssl_context=ssl_context, + verify_ssl=configuration.verify_ssl + ) + + # https pool manager + if configuration.proxy: + self.pool_manager = aiohttp.ClientSession( + connector=connector, + proxy=configuration.proxy + ) + else: + self.pool_manager = aiohttp.ClientSession( + connector=connector + ) + + async def request(self, method, url, query_params=None, headers=None, + body=None, post_params=None, _preload_content=True, + _request_timeout=None): + """Execute request + + :param method: http request method + :param url: http request url + :param query_params: query parameters in the url + :param headers: http request headers + :param body: request json body, for `application/json` + :param post_params: request post parameters, + `application/x-www-form-urlencoded` + and `multipart/form-data` + :param _preload_content: this is a non-applicable field for + the AiohttpClient. + :param _request_timeout: timeout setting for this request. If one + number provided, it will be total request + timeout. It can also be a pair (tuple) of + (connection, read) timeouts. + """ + method = method.upper() + assert method in ['GET', 'HEAD', 'DELETE', 'POST', 'PUT', + 'PATCH', 'OPTIONS'] + + if post_params and body: + raise ValueError( + "body parameter cannot be used with post_params parameter." + ) + + post_params = post_params or {} + headers = headers or {} + timeout = _request_timeout or 5 * 60 + + if 'Content-Type' not in headers: + headers['Content-Type'] = 'application/json' + + args = { + "method": method, + "url": url, + "timeout": timeout, + "headers": headers + } + + if query_params: + args["url"] += '?' + urlencode(query_params) + + # For `POST`, `PUT`, `PATCH`, `OPTIONS`, `DELETE` + if method in ['POST', 'PUT', 'PATCH', 'OPTIONS', 'DELETE']: + if re.search('json', headers['Content-Type'], re.IGNORECASE): + if body is not None: + body = json.dumps(body) + args["data"] = body + elif headers['Content-Type'] == 'application/x-www-form-urlencoded': # noqa: E501 + args["data"] = aiohttp.FormData(post_params) + elif headers['Content-Type'] == 'multipart/form-data': + # must del headers['Content-Type'], or the correct + # Content-Type which generated by aiohttp + del headers['Content-Type'] + data = aiohttp.FormData() + for param in post_params: + k, v = param + if isinstance(v, tuple) and len(v) == 3: + data.add_field(k, + value=v[1], + filename=v[0], + content_type=v[2]) + else: + data.add_field(k, v) + args["data"] = data + + # Pass a `bytes` parameter directly in the body to support + # other content types than Json when `body` argument is provided + # in serialized form + elif isinstance(body, bytes): + args["data"] = body + else: + # Cannot generate the request from given parameters + msg = """Cannot prepare a request message for provided + arguments. Please check that your arguments match + declared content type.""" + raise ApiException(status=0, reason=msg) + + async with self.pool_manager.request(**args) as r: + data = await r.text() + r = RESTResponse(r, data) + + # log response body + logger.debug("response body: %s", r.data) + + if not 200 <= r.status <= 299: + raise ApiException(http_resp=r) + + return r + + async def GET(self, url, headers=None, query_params=None, + _preload_content=True, _request_timeout=None): + return (await self.request("GET", url, + headers=headers, + _preload_content=_preload_content, + _request_timeout=_request_timeout, + query_params=query_params)) + + async def HEAD(self, url, headers=None, query_params=None, + _preload_content=True, _request_timeout=None): + return (await self.request("HEAD", url, + headers=headers, + _preload_content=_preload_content, + _request_timeout=_request_timeout, + query_params=query_params)) + + async def OPTIONS(self, url, headers=None, query_params=None, + post_params=None, body=None, _preload_content=True, + _request_timeout=None): + return (await self.request("OPTIONS", url, + headers=headers, + query_params=query_params, + post_params=post_params, + _preload_content=_preload_content, + _request_timeout=_request_timeout, + body=body)) + + async def DELETE(self, url, headers=None, query_params=None, body=None, + _preload_content=True, _request_timeout=None): + return (await self.request("DELETE", url, + headers=headers, + query_params=query_params, + _preload_content=_preload_content, + _request_timeout=_request_timeout, + body=body)) + + async def POST(self, url, headers=None, query_params=None, + post_params=None, body=None, _preload_content=True, + _request_timeout=None): + return (await self.request("POST", url, + headers=headers, + query_params=query_params, + post_params=post_params, + _preload_content=_preload_content, + _request_timeout=_request_timeout, + body=body)) + + async def PUT(self, url, headers=None, query_params=None, post_params=None, + body=None, _preload_content=True, _request_timeout=None): + return (await self.request("PUT", url, + headers=headers, + query_params=query_params, + post_params=post_params, + _preload_content=_preload_content, + _request_timeout=_request_timeout, + body=body)) + + async def PATCH(self, url, headers=None, query_params=None, + post_params=None, body=None, _preload_content=True, + _request_timeout=None): + return (await self.request("PATCH", url, + headers=headers, + query_params=query_params, + post_params=post_params, + _preload_content=_preload_content, + _request_timeout=_request_timeout, + body=body)) + + +class ApiException(Exception): + + def __init__(self, status=None, reason=None, http_resp=None): + if http_resp: + self.status = http_resp.status + self.reason = http_resp.reason + self.body = http_resp.data + self.headers = http_resp.getheaders() + else: + self.status = status + self.reason = reason + self.body = None + self.headers = None + + def __str__(self): + """Custom error messages for exception""" + error_message = "({0})\nReason: {1}\n".format(self.status, self.reason) + if self.headers: + error_message += "HTTP response headers: {0}\n".format( + self.headers) + + if self.body: + error_message += "HTTP response body: {0}\n".format(self.body) + + return error_message diff --git a/src/main/resources/mustache/python/configuration.mustache b/src/main/resources/mustache/python/configuration.mustache new file mode 100644 index 0000000000..84e6ad4d20 --- /dev/null +++ b/src/main/resources/mustache/python/configuration.mustache @@ -0,0 +1,260 @@ +# coding: utf-8 + +{{>partial_header}} + +from __future__ import absolute_import + +import copy +import logging +import multiprocessing +import sys +import urllib3 + +import six +from six.moves import http_client as httplib + + +class TypeWithDefault(type): + def __init__(cls, name, bases, dct): + super(TypeWithDefault, cls).__init__(name, bases, dct) + cls._default = None + + def __call__(cls): + if cls._default is None: + cls._default = type.__call__(cls) + return copy.copy(cls._default) + + def set_default(cls, default): + cls._default = copy.copy(default) + + +class Configuration(six.with_metaclass(TypeWithDefault, object)): + """NOTE: This class is auto generated by the swagger code generator program. + + Ref: https://github.com/swagger-api/swagger-codegen + Do not edit the class manually. + """ + + def __init__(self): + """Constructor""" + # Default Base url + self.host = "{{{basePath}}}" + # Temp file folder for downloading files + self.temp_folder_path = None + + # Authentication Settings + # dict to store API key(s) + self.api_key = {} + # dict to store API prefix (e.g. Bearer) + self.api_key_prefix = {} + # Username for HTTP basic authentication + self.username = "" + # Password for HTTP basic authentication + self.password = "" +{{#authMethods}}{{#isOAuth}} + # access token for OAuth + self.access_token = "" +{{/isOAuth}}{{/authMethods}} + # Logging Settings + self.logger = {} + self.logger["package_logger"] = logging.getLogger("{{packageName}}") + self.logger["urllib3_logger"] = logging.getLogger("urllib3") + # Log format + self.logger_format = '%(asctime)s %(levelname)s %(message)s' + # Log stream handler + self.logger_stream_handler = None + # Log file handler + self.logger_file_handler = None + # Debug file location + self.logger_file = None + # Debug switch + self.debug = False + + # SSL/TLS verification + # Set this to false to skip verifying SSL certificate when calling API + # from https server. + self.verify_ssl = True + # Set this to customize the certificate file to verify the peer. + self.ssl_ca_cert = None + # client certificate file + self.cert_file = None + # client key file + self.key_file = None + # Set this to True/False to enable/disable SSL hostname verification. + self.assert_hostname = None + + # urllib3 connection pool's maximum number of connections saved + # per pool. urllib3 uses 1 connection as default value, but this is + # not the best value when you are making a lot of possibly parallel + # requests to the same host, which is often the case here. + # cpu_count * 5 is used as default value to increase performance. + self.connection_pool_maxsize = multiprocessing.cpu_count() * 5 + + # Proxy URL + self.proxy = None + # Safe chars for path_param + self.safe_chars_for_path_param = '' + + @property + def logger_file(self): + """The logger file. + + If the logger_file is None, then add stream handler and remove file + handler. Otherwise, add file handler and remove stream handler. + + :param value: The logger_file path. + :type: str + """ + return self.__logger_file + + @logger_file.setter + def logger_file(self, value): + """The logger file. + + If the logger_file is None, then add stream handler and remove file + handler. Otherwise, add file handler and remove stream handler. + + :param value: The logger_file path. + :type: str + """ + self.__logger_file = value + if self.__logger_file: + # If set logging file, + # then add file handler and remove stream handler. + self.logger_file_handler = logging.FileHandler(self.__logger_file) + self.logger_file_handler.setFormatter(self.logger_formatter) + for _, logger in six.iteritems(self.logger): + logger.addHandler(self.logger_file_handler) + if self.logger_stream_handler: + logger.removeHandler(self.logger_stream_handler) + else: + # If not set logging file, + # then add stream handler and remove file handler. + self.logger_stream_handler = logging.StreamHandler() + self.logger_stream_handler.setFormatter(self.logger_formatter) + for _, logger in six.iteritems(self.logger): + logger.addHandler(self.logger_stream_handler) + if self.logger_file_handler: + logger.removeHandler(self.logger_file_handler) + + @property + def debug(self): + """Debug status + + :param value: The debug status, True or False. + :type: bool + """ + return self.__debug + + @debug.setter + def debug(self, value): + """Debug status + + :param value: The debug status, True or False. + :type: bool + """ + self.__debug = value + if self.__debug: + # if debug status is True, turn on debug logging + for _, logger in six.iteritems(self.logger): + logger.setLevel(logging.DEBUG) + # turn on httplib debug + httplib.HTTPConnection.debuglevel = 1 + else: + # if debug status is False, turn off debug logging, + # setting log level to default `logging.WARNING` + for _, logger in six.iteritems(self.logger): + logger.setLevel(logging.WARNING) + # turn off httplib debug + httplib.HTTPConnection.debuglevel = 0 + + @property + def logger_format(self): + """The logger format. + + The logger_formatter will be updated when sets logger_format. + + :param value: The format string. + :type: str + """ + return self.__logger_format + + @logger_format.setter + def logger_format(self, value): + """The logger format. + + The logger_formatter will be updated when sets logger_format. + + :param value: The format string. + :type: str + """ + self.__logger_format = value + self.logger_formatter = logging.Formatter(self.__logger_format) + + def get_api_key_with_prefix(self, identifier): + """Gets API key (with prefix if set). + + :param identifier: The identifier of apiKey. + :return: The token for api key authentication. + """ + if (self.api_key.get(identifier) and + self.api_key_prefix.get(identifier)): + return self.api_key_prefix[identifier] + ' ' + self.api_key[identifier] # noqa: E501 + elif self.api_key.get(identifier): + return self.api_key[identifier] + + def get_basic_auth_token(self): + """Gets HTTP basic authentication header (string). + + :return: The token for basic HTTP authentication. + """ + return urllib3.util.make_headers( + basic_auth=self.username + ':' + self.password + ).get('authorization') + + def auth_settings(self): + """Gets Auth Settings dict for api client. + + :return: The Auth Settings information dict. + """ + return { +{{#authMethods}} +{{#isApiKey}} + '{{name}}': + { + 'type': 'api_key', + 'in': {{#isKeyInHeader}}'header'{{/isKeyInHeader}}{{#isKeyInQuery}}'query'{{/isKeyInQuery}}, + 'key': '{{keyParamName}}', + 'value': self.get_api_key_with_prefix('{{keyParamName}}') + }, +{{/isApiKey}} +{{#isBasic}} + '{{name}}': + { + 'type': 'basic', + 'in': 'header', + 'key': 'Authorization', + 'value': self.get_basic_auth_token() + }, +{{/isBasic}}{{#isOAuth}} + '{{name}}': + { + 'type': 'oauth2', + 'in': 'header', + 'key': 'Authorization', + 'value': 'Bearer ' + self.access_token + }, +{{/isOAuth}}{{/authMethods}} + } + + def to_debug_report(self): + """Gets the essential information for debugging. + + :return: The report for debugging. + """ + return "Python SDK Debug Report:\n"\ + "OS: {env}\n"\ + "Python Version: {pyversion}\n"\ + "Version of the API: {{version}}\n"\ + "SDK Package Version: {{packageVersion}}".\ + format(env=sys.platform, pyversion=sys.version) diff --git a/src/main/resources/mustache/python/git_push.sh.mustache b/src/main/resources/mustache/python/git_push.sh.mustache new file mode 100755 index 0000000000..a2d7523483 --- /dev/null +++ b/src/main/resources/mustache/python/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/python/gitignore.mustache b/src/main/resources/mustache/python/gitignore.mustache new file mode 100644 index 0000000000..a655050c26 --- /dev/null +++ b/src/main/resources/mustache/python/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/python/model.mustache b/src/main/resources/mustache/python/model.mustache new file mode 100644 index 0000000000..3fc2747fe1 --- /dev/null +++ b/src/main/resources/mustache/python/model.mustache @@ -0,0 +1,222 @@ +# coding: utf-8 + +{{>partial_header}} + +import pprint +import re # noqa: F401 + +import six +{{#imports}}{{#-first}} +{{/-first}} +{{import}} # noqa: F401,E501 +{{/imports}} + + +{{#models}} +{{#model}} +class {{classname}}({{#parent}}{{parent}}{{/parent}}{{^parent}}object{{/parent}}): + """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}} + + """ + Attributes: + swagger_types (dict): The key is attribute name + and the value is attribute type. + attribute_map (dict): The key is attribute name + and the value is json key in definition. + """ + swagger_types = { +{{#vars}} + '{{name}}': '{{{datatype}}}'{{#hasMore}},{{/hasMore}} +{{/vars}} + } + + attribute_map = { +{{#vars}} + '{{name}}': '{{baseName}}'{{#hasMore}},{{/hasMore}} +{{/vars}} + } +{{#discriminator}} + + discriminator_value_class_map = { + {{#children}}'{{^vendorExtensions.x-discriminator-value}}{{name}}{{/vendorExtensions.x-discriminator-value}}{{#vendorExtensions.x-discriminator-value}}{{{vendorExtensions.x-discriminator-value}}}{{/vendorExtensions.x-discriminator-value}}': '{{{classname}}}'{{^-last}}, + {{/-last}}{{/children}} + } +{{/discriminator}} + + def __init__(self{{#vars}}, {{name}}={{#defaultValue}}{{{defaultValue}}}{{/defaultValue}}{{^defaultValue}}None{{/defaultValue}}{{/vars}}): # noqa: E501 + """{{classname}} - a model defined in Swagger""" # noqa: E501 +{{#vars}}{{#-first}} +{{/-first}} + self._{{name}} = None +{{/vars}} + self.discriminator = {{#discriminator}}'{{discriminator}}'{{/discriminator}}{{^discriminator}}None{{/discriminator}} +{{#vars}}{{#-first}} +{{/-first}} +{{#required}} + self.{{name}} = {{name}} +{{/required}} +{{^required}} + if {{name}} is not None: + self.{{name}} = {{name}} +{{/required}} +{{/vars}} + +{{#vars}} + @property + def {{name}}(self): + """Gets the {{name}} of this {{classname}}. # noqa: E501 + +{{#description}} + {{{description}}} # noqa: E501 +{{/description}} + + :return: The {{name}} of this {{classname}}. # noqa: E501 + :rtype: {{datatype}} + """ + return self._{{name}} + + @{{name}}.setter + def {{name}}(self, {{name}}): + """Sets the {{name}} of this {{classname}}. + +{{#description}} + {{{description}}} # noqa: E501 +{{/description}} + + :param {{name}}: The {{name}} of this {{classname}}. # noqa: E501 + :type: {{datatype}} + """ +{{#required}} + if {{name}} is None: + raise ValueError("Invalid value for `{{name}}`, must not be `None`") # noqa: E501 +{{/required}} +{{#isEnum}} +{{#isContainer}} + allowed_values = [{{#allowableValues}}{{#values}}{{#items.isString}}"{{/items.isString}}{{{this}}}{{#items.isString}}"{{/items.isString}}{{^-last}}, {{/-last}}{{/values}}{{/allowableValues}}] # noqa: E501 +{{#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}} + allowed_values = [{{#allowableValues}}{{#values}}{{#isString}}"{{/isString}}{{{this}}}{{#isString}}"{{/isString}}{{^-last}}, {{/-last}}{{/values}}{{/allowableValues}}] # noqa: E501 + if {{{name}}} not in allowed_values: + raise ValueError( + "Invalid value for `{{{name}}}` ({0}), must be one of {1}" # noqa: E501 + .format({{{name}}}, allowed_values) + ) +{{/isContainer}} +{{/isEnum}} +{{^isEnum}} +{{#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(r"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}} + +{{/vars}} +{{#discriminator}} + def get_real_child_model(self, data): + """Returns the real base class specified by the discriminator""" + discriminator_value = data[self.discriminator].lower() + return self.discriminator_value_class_map.get(discriminator_value) + +{{/discriminator}} + def to_dict(self): + """Returns the model properties as a 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 + if issubclass({{classname}}, dict): + for key, value in self.items(): + result[key] = value + + return result + + def to_str(self): + """Returns the string representation of the model""" + return pprint.pformat(self.to_dict()) + + def __repr__(self): + """For `print` and `pprint`""" + return self.to_str() + + def __eq__(self, other): + """Returns true if both objects are equal""" + if not isinstance(other, {{classname}}): + return False + + return self.__dict__ == other.__dict__ + + def __ne__(self, other): + """Returns true if both objects are not equal""" + return not self == other +{{/model}} +{{/models}} diff --git a/src/main/resources/mustache/python/model_doc.mustache b/src/main/resources/mustache/python/model_doc.mustache new file mode 100644 index 0000000000..569550df37 --- /dev/null +++ b/src/main/resources/mustache/python/model_doc.mustache @@ -0,0 +1,11 @@ +{{#models}}{{#model}}# {{classname}} + +## Properties +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +{{#vars}}**{{name}}** | {{#isPrimitiveType}}**{{datatype}}**{{/isPrimitiveType}}{{^isPrimitiveType}}[**{{datatype}}**]({{complexType}}.md){{/isPrimitiveType}} | {{description}} | {{^required}}[optional] {{/required}}{{#readOnly}}[readonly] {{/readOnly}}{{#defaultValue}}[default to {{{.}}}]{{/defaultValue}} +{{/vars}} + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + +{{/model}}{{/models}} diff --git a/src/main/resources/mustache/python/model_test.mustache b/src/main/resources/mustache/python/model_test.mustache new file mode 100644 index 0000000000..765e4f9c2c --- /dev/null +++ b/src/main/resources/mustache/python/model_test.mustache @@ -0,0 +1,35 @@ +# coding: utf-8 + +{{>partial_header}} + +from __future__ import absolute_import + +import unittest + +{{#models}} +{{#model}} +import {{packageName}} +from {{modelPackage}}.{{classFilename}} import {{classname}} # noqa: E501 +from {{packageName}}.rest import ApiException + + +class Test{{classname}}(unittest.TestCase): + """{{classname}} unit test stubs""" + + def setUp(self): + pass + + def tearDown(self): + pass + + def test{{classname}}(self): + """Test {{classname}}""" + # FIXME: construct object with mandatory attributes with example values + # model = {{packageName}}.models.{{classFilename}}.{{classname}}() # noqa: E501 + pass + +{{/model}} +{{/models}} + +if __name__ == '__main__': + unittest.main() diff --git a/src/main/resources/mustache/python/partial_header.mustache b/src/main/resources/mustache/python/partial_header.mustache new file mode 100644 index 0000000000..513ad04cdb --- /dev/null +++ b/src/main/resources/mustache/python/partial_header.mustache @@ -0,0 +1,13 @@ +""" +{{#appName}} + {{{appName}}} +{{/appName}} + +{{#appDescription}} + {{{appDescription}}} # noqa: E501 +{{/appDescription}} + + {{#version}}OpenAPI spec version: {{{version}}}{{/version}} + {{#infoEmail}}Contact: {{{infoEmail}}}{{/infoEmail}} + Generated by: https://github.com/swagger-api/swagger-codegen.git +""" diff --git a/src/main/resources/mustache/python/requirements.mustache b/src/main/resources/mustache/python/requirements.mustache new file mode 100644 index 0000000000..bafdc07532 --- /dev/null +++ b/src/main/resources/mustache/python/requirements.mustache @@ -0,0 +1,5 @@ +certifi >= 14.05.14 +six >= 1.10 +python_dateutil >= 2.5.3 +setuptools >= 21.0.0 +urllib3 >= 1.15.1 diff --git a/src/main/resources/mustache/python/rest.mustache b/src/main/resources/mustache/python/rest.mustache new file mode 100644 index 0000000000..ff9b019dc5 --- /dev/null +++ b/src/main/resources/mustache/python/rest.mustache @@ -0,0 +1,314 @@ +# coding: utf-8 + +{{>partial_header}} + +from __future__ import absolute_import + +import io +import json +import logging +import re +import ssl + +import certifi +# python 2 and python 3 compatibility library +import six +from six.moves.urllib.parse import urlencode + +try: + import urllib3 +except ImportError: + raise ImportError('Swagger python client requires urllib3.') + + +logger = logging.getLogger(__name__) + + +class RESTResponse(io.IOBase): + + def __init__(self, resp): + self.urllib3_response = resp + self.status = resp.status + self.reason = resp.reason + self.data = resp.data + + def getheaders(self): + """Returns a dictionary of the response headers.""" + return self.urllib3_response.getheaders() + + def getheader(self, name, default=None): + """Returns a given response header.""" + return self.urllib3_response.getheader(name, default) + + +class RESTClientObject(object): + + def __init__(self, configuration, pools_size=4, maxsize=None): + # urllib3.PoolManager will pass all kw parameters to connectionpool + # https://github.com/shazow/urllib3/blob/f9409436f83aeb79fbaf090181cd81b784f1b8ce/urllib3/poolmanager.py#L75 # noqa: E501 + # https://github.com/shazow/urllib3/blob/f9409436f83aeb79fbaf090181cd81b784f1b8ce/urllib3/connectionpool.py#L680 # noqa: E501 + # maxsize is the number of requests to host that are allowed in parallel # noqa: E501 + # Custom SSL certificates and client certificates: http://urllib3.readthedocs.io/en/latest/advanced-usage.html # noqa: E501 + + # cert_reqs + if configuration.verify_ssl: + cert_reqs = ssl.CERT_REQUIRED + else: + cert_reqs = ssl.CERT_NONE + + # ca_certs + if configuration.ssl_ca_cert: + ca_certs = configuration.ssl_ca_cert + else: + # if not set certificate file, use Mozilla's root certificates. + ca_certs = certifi.where() + + addition_pool_args = {} + if configuration.assert_hostname is not None: + addition_pool_args['assert_hostname'] = configuration.assert_hostname # noqa: E501 + + if maxsize is None: + if configuration.connection_pool_maxsize is not None: + maxsize = configuration.connection_pool_maxsize + else: + maxsize = 4 + + # https pool manager + if configuration.proxy: + self.pool_manager = urllib3.ProxyManager( + num_pools=pools_size, + maxsize=maxsize, + cert_reqs=cert_reqs, + ca_certs=ca_certs, + cert_file=configuration.cert_file, + key_file=configuration.key_file, + proxy_url=configuration.proxy, + **addition_pool_args + ) + else: + self.pool_manager = urllib3.PoolManager( + num_pools=pools_size, + maxsize=maxsize, + cert_reqs=cert_reqs, + ca_certs=ca_certs, + cert_file=configuration.cert_file, + key_file=configuration.key_file, + **addition_pool_args + ) + + def request(self, method, url, query_params=None, headers=None, + body=None, post_params=None, _preload_content=True, + _request_timeout=None): + """Perform requests. + + :param method: http request method + :param url: http request url + :param query_params: query parameters in the url + :param headers: http request headers + :param body: request json body, for `application/json` + :param post_params: request post parameters, + `application/x-www-form-urlencoded` + and `multipart/form-data` + :param _preload_content: if False, the urllib3.HTTPResponse object will + be returned without reading/decoding response + data. Default is True. + :param _request_timeout: timeout setting for this request. If one + number provided, it will be total request + timeout. It can also be a pair (tuple) of + (connection, read) timeouts. + """ + method = method.upper() + assert method in ['GET', 'HEAD', 'DELETE', 'POST', 'PUT', + 'PATCH', 'OPTIONS'] + + if post_params and body: + raise ValueError( + "body parameter cannot be used with post_params parameter." + ) + + post_params = post_params or {} + headers = headers or {} + + timeout = None + if _request_timeout: + if isinstance(_request_timeout, (int, ) if six.PY3 else (int, long)): # noqa: E501,F821 + timeout = urllib3.Timeout(total=_request_timeout) + elif (isinstance(_request_timeout, tuple) and + len(_request_timeout) == 2): + timeout = urllib3.Timeout( + connect=_request_timeout[0], read=_request_timeout[1]) + + if 'Content-Type' not in headers: + headers['Content-Type'] = 'application/json' + + try: + # For `POST`, `PUT`, `PATCH`, `OPTIONS`, `DELETE` + if method in ['POST', 'PUT', 'PATCH', 'OPTIONS', 'DELETE']: + if query_params: + url += '?' + urlencode(query_params) + if re.search('json', headers['Content-Type'], re.IGNORECASE): + request_body = None + if body is not None: + request_body = json.dumps(body) + r = self.pool_manager.request( + method, url, + body=request_body, + preload_content=_preload_content, + timeout=timeout, + headers=headers) + elif headers['Content-Type'] == 'application/x-www-form-urlencoded': # noqa: E501 + r = self.pool_manager.request( + method, url, + fields=post_params, + encode_multipart=False, + preload_content=_preload_content, + timeout=timeout, + headers=headers) + elif headers['Content-Type'] == 'multipart/form-data': + # must del headers['Content-Type'], or the correct + # Content-Type which generated by urllib3 will be + # overwritten. + del headers['Content-Type'] + r = self.pool_manager.request( + method, url, + fields=post_params, + encode_multipart=True, + preload_content=_preload_content, + timeout=timeout, + headers=headers) + # Pass a `string` parameter directly in the body to support + # other content types than Json when `body` argument is + # provided in serialized form + elif isinstance(body, str): + request_body = body + r = self.pool_manager.request( + method, url, + body=request_body, + preload_content=_preload_content, + timeout=timeout, + headers=headers) + else: + # Cannot generate the request from given parameters + msg = """Cannot prepare a request message for provided + arguments. Please check that your arguments match + declared content type.""" + raise ApiException(status=0, reason=msg) + # For `GET`, `HEAD` + else: + r = self.pool_manager.request(method, url, + fields=query_params, + preload_content=_preload_content, + timeout=timeout, + headers=headers) + except urllib3.exceptions.SSLError as e: + msg = "{0}\n{1}".format(type(e).__name__, str(e)) + raise ApiException(status=0, reason=msg) + + if _preload_content: + r = RESTResponse(r) + + # In the python 3, the response.data is bytes. + # we need to decode it to string. + if six.PY3: + r.data = r.data.decode('utf8') + + # log response body + logger.debug("response body: %s", r.data) + + if not 200 <= r.status <= 299: + raise ApiException(http_resp=r) + + return r + + def GET(self, url, headers=None, query_params=None, _preload_content=True, + _request_timeout=None): + return self.request("GET", url, + headers=headers, + _preload_content=_preload_content, + _request_timeout=_request_timeout, + query_params=query_params) + + def HEAD(self, url, headers=None, query_params=None, _preload_content=True, + _request_timeout=None): + return self.request("HEAD", url, + headers=headers, + _preload_content=_preload_content, + _request_timeout=_request_timeout, + query_params=query_params) + + def OPTIONS(self, url, headers=None, query_params=None, post_params=None, + body=None, _preload_content=True, _request_timeout=None): + return self.request("OPTIONS", url, + headers=headers, + query_params=query_params, + post_params=post_params, + _preload_content=_preload_content, + _request_timeout=_request_timeout, + body=body) + + def DELETE(self, url, headers=None, query_params=None, body=None, + _preload_content=True, _request_timeout=None): + return self.request("DELETE", url, + headers=headers, + query_params=query_params, + _preload_content=_preload_content, + _request_timeout=_request_timeout, + body=body) + + def POST(self, url, headers=None, query_params=None, post_params=None, + body=None, _preload_content=True, _request_timeout=None): + return self.request("POST", url, + headers=headers, + query_params=query_params, + post_params=post_params, + _preload_content=_preload_content, + _request_timeout=_request_timeout, + body=body) + + def PUT(self, url, headers=None, query_params=None, post_params=None, + body=None, _preload_content=True, _request_timeout=None): + return self.request("PUT", url, + headers=headers, + query_params=query_params, + post_params=post_params, + _preload_content=_preload_content, + _request_timeout=_request_timeout, + body=body) + + def PATCH(self, url, headers=None, query_params=None, post_params=None, + body=None, _preload_content=True, _request_timeout=None): + return self.request("PATCH", url, + headers=headers, + query_params=query_params, + post_params=post_params, + _preload_content=_preload_content, + _request_timeout=_request_timeout, + body=body) + + +class ApiException(Exception): + + def __init__(self, status=None, reason=None, http_resp=None): + if http_resp: + self.status = http_resp.status + self.reason = http_resp.reason + self.body = http_resp.data + self.headers = http_resp.getheaders() + else: + self.status = status + self.reason = reason + self.body = None + self.headers = None + + def __str__(self): + """Custom error messages for exception""" + error_message = "({0})\n"\ + "Reason: {1}\n".format(self.status, self.reason) + if self.headers: + error_message += "HTTP response headers: {0}\n".format( + self.headers) + + if self.body: + error_message += "HTTP response body: {0}\n".format(self.body) + + return error_message diff --git a/src/main/resources/mustache/python/setup.mustache b/src/main/resources/mustache/python/setup.mustache new file mode 100644 index 0000000000..e7107e9be9 --- /dev/null +++ b/src/main/resources/mustache/python/setup.mustache @@ -0,0 +1,43 @@ +# coding: utf-8 + +{{>partial_header}} + +from setuptools import setup, find_packages # noqa: H301 + +NAME = "{{{projectName}}}" +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 = ["urllib3 >= 1.15", "six >= 1.10", "certifi", "python-dateutil"] +{{#asyncio}} +REQUIRES.append("aiohttp") +{{/asyncio}} +{{#tornado}} +REQUIRES.append("tornado") +{{/tornado}} + +setup( + name=NAME, + version=VERSION, + description="{{appName}}", + author_email="{{infoEmail}}", + url="{{packageUrl}}", + keywords=["Swagger", "{{appName}}"], + install_requires=REQUIRES, + packages=find_packages(), + include_package_data=True, + long_description="""\ + {{appDescription}} # noqa: E501 + """ +) +{{/hasMore}} +{{/apis}} +{{/apiInfo}} diff --git a/src/main/resources/mustache/python/test-requirements.mustache b/src/main/resources/mustache/python/test-requirements.mustache new file mode 100644 index 0000000000..31f8d94d99 --- /dev/null +++ b/src/main/resources/mustache/python/test-requirements.mustache @@ -0,0 +1,11 @@ +{{^asyncio}} +coverage>=4.0.3 +nose>=1.3.7 +{{/asyncio}} +{{#asyncio}} +pytest>=3.3.1 +pytest-cov>=2.5.1 +{{/asyncio}} +pluggy>=0.3.1 +py>=1.4.31 +randomize>=0.13 diff --git a/src/main/resources/mustache/python/tornado/rest.mustache b/src/main/resources/mustache/python/tornado/rest.mustache new file mode 100644 index 0000000000..bc74fbc295 --- /dev/null +++ b/src/main/resources/mustache/python/tornado/rest.mustache @@ -0,0 +1,255 @@ +# coding: utf-8 + +{{>partial_header}} + +import io +import json +import logging +import re + +# python 2 and python 3 compatibility library +import six +from six.moves.urllib.parse import urlencode +import tornado +import tornado.gen +from tornado import httpclient +from urllib3.filepost import encode_multipart_formdata + +logger = logging.getLogger(__name__) + + +class RESTResponse(io.IOBase): + + def __init__(self, resp): + self.tornado_response = resp + self.status = resp.code + self.reason = resp.reason + + if resp.body: + # In Python 3, the response body is utf-8 encoded bytes. + if six.PY3: + self.data = resp.body.decode('utf-8') + else: + self.data = resp.body + else: + self.data = None + + def getheaders(self): + """Returns a CIMultiDictProxy of the response headers.""" + return self.tornado_response.headers + + def getheader(self, name, default=None): + """Returns a given response header.""" + return self.tornado_response.headers.get(name, default) + + +class RESTClientObject(object): + + def __init__(self, configuration, pools_size=4, maxsize=4): + # maxsize is number of requests to host that are allowed in parallel + + self.ca_certs = configuration.ssl_ca_cert + self.client_key = configuration.key_file + self.client_cert = configuration.cert_file + + self.proxy_port = self.proxy_host = None + + # https pool manager + if configuration.proxy: + self.proxy_port = 80 + self.proxy_host = configuration.proxy + + self.pool_manager = httpclient.AsyncHTTPClient() + + @tornado.gen.coroutine + def request(self, method, url, query_params=None, headers=None, body=None, + post_params=None, _preload_content=True, + _request_timeout=None): + """Execute Request + + :param method: http request method + :param url: http request url + :param query_params: query parameters in the url + :param headers: http request headers + :param body: request json body, for `application/json` + :param post_params: request post parameters, + `application/x-www-form-urlencoded` + and `multipart/form-data` + :param _preload_content: this is a non-applicable field for + the AiohttpClient. + :param _request_timeout: timeout setting for this request. If one + number provided, it will be total request + timeout. It can also be a pair (tuple) of + (connection, read) timeouts. + """ + method = method.upper() + assert method in ['GET', 'HEAD', 'DELETE', 'POST', 'PUT', + 'PATCH', 'OPTIONS'] + + if post_params and body: + raise ValueError( + "body parameter cannot be used with post_params parameter." + ) + + request = httpclient.HTTPRequest(url) + request.allow_nonstandard_methods = True + request.ca_certs = self.ca_certs + request.client_key = self.client_key + request.client_cert = self.client_cert + request.proxy_host = self.proxy_host + request.proxy_port = self.proxy_port + request.method = method + if headers: + request.headers = headers + if 'Content-Type' not in headers: + request.headers['Content-Type'] = 'application/json' + request.request_timeout = _request_timeout or 5 * 60 + + post_params = post_params or {} + + if query_params: + request.url += '?' + urlencode(query_params) + + # For `POST`, `PUT`, `PATCH`, `OPTIONS`, `DELETE` + if method in ['POST', 'PUT', 'PATCH', 'OPTIONS', 'DELETE']: + if re.search('json', headers['Content-Type'], re.IGNORECASE): + if body: + body = json.dumps(body) + request.body = body + elif headers['Content-Type'] == 'application/x-www-form-urlencoded': # noqa: E501 + request.body = urlencode(post_params) + elif headers['Content-Type'] == 'multipart/form-data': + multipart = encode_multipart_formdata(post_params) + request.body, headers['Content-Type'] = multipart + # Pass a `bytes` parameter directly in the body to support + # other content types than Json when `body` argument is provided + # in serialized form + elif isinstance(body, bytes): + request.body = body + else: + # Cannot generate the request from given parameters + msg = """Cannot prepare a request message for provided + arguments. Please check that your arguments match + declared content type.""" + raise ApiException(status=0, reason=msg) + + r = yield self.pool_manager.fetch(request, raise_error=False) + + if _preload_content: + + r = RESTResponse(r) + + # log response body + logger.debug("response body: %s", r.data) + + if not 200 <= r.status <= 299: + raise ApiException(http_resp=r) + + raise tornado.gen.Return(r) + + @tornado.gen.coroutine + def GET(self, url, headers=None, query_params=None, _preload_content=True, + _request_timeout=None): + result = yield self.request("GET", url, + headers=headers, + _preload_content=_preload_content, + _request_timeout=_request_timeout, + query_params=query_params) + raise tornado.gen.Return(result) + + @tornado.gen.coroutine + def HEAD(self, url, headers=None, query_params=None, _preload_content=True, + _request_timeout=None): + result = yield self.request("HEAD", url, + headers=headers, + _preload_content=_preload_content, + _request_timeout=_request_timeout, + query_params=query_params) + raise tornado.gen.Return(result) + + @tornado.gen.coroutine + def OPTIONS(self, url, headers=None, query_params=None, post_params=None, + body=None, _preload_content=True, _request_timeout=None): + result = yield self.request("OPTIONS", url, + headers=headers, + query_params=query_params, + post_params=post_params, + _preload_content=_preload_content, + _request_timeout=_request_timeout, + body=body) + raise tornado.gen.Return(result) + + @tornado.gen.coroutine + def DELETE(self, url, headers=None, query_params=None, body=None, + _preload_content=True, _request_timeout=None): + result = yield self.request("DELETE", url, + headers=headers, + query_params=query_params, + _preload_content=_preload_content, + _request_timeout=_request_timeout, + body=body) + raise tornado.gen.Return(result) + + @tornado.gen.coroutine + def POST(self, url, headers=None, query_params=None, post_params=None, + body=None, _preload_content=True, _request_timeout=None): + result = yield self.request("POST", url, + headers=headers, + query_params=query_params, + post_params=post_params, + _preload_content=_preload_content, + _request_timeout=_request_timeout, + body=body) + raise tornado.gen.Return(result) + + @tornado.gen.coroutine + def PUT(self, url, headers=None, query_params=None, post_params=None, + body=None, _preload_content=True, _request_timeout=None): + result = yield self.request("PUT", url, + headers=headers, + query_params=query_params, + post_params=post_params, + _preload_content=_preload_content, + _request_timeout=_request_timeout, + body=body) + raise tornado.gen.Return(result) + + @tornado.gen.coroutine + def PATCH(self, url, headers=None, query_params=None, post_params=None, + body=None, _preload_content=True, _request_timeout=None): + result = yield self.request("PATCH", url, + headers=headers, + query_params=query_params, + post_params=post_params, + _preload_content=_preload_content, + _request_timeout=_request_timeout, + body=body) + raise tornado.gen.Return(result) + + +class ApiException(Exception): + + def __init__(self, status=None, reason=None, http_resp=None): + if http_resp: + self.status = http_resp.status + self.reason = http_resp.reason + self.body = http_resp.data + self.headers = http_resp.getheaders() + else: + self.status = status + self.reason = reason + self.body = None + self.headers = None + + def __str__(self): + """Custom error messages for exception""" + error_message = "({0})\nReason: {1}\n".format( + self.status, self.reason) + if self.headers: + error_message += "HTTP response headers: {0}\n".format( + self.headers) + + if self.body: + error_message += "HTTP response body: {0}\n".format(self.body) + + return error_message diff --git a/src/main/resources/mustache/python/tox.mustache b/src/main/resources/mustache/python/tox.mustache new file mode 100644 index 0000000000..63d12fdeae --- /dev/null +++ b/src/main/resources/mustache/python/tox.mustache @@ -0,0 +1,20 @@ +[tox] +{{^asyncio}} +envlist = py27, py3 +{{/asyncio}} +{{#asyncio}} +envlist = py3 +{{/asyncio}} + +[testenv] +deps=-r{toxinidir}/requirements.txt + -r{toxinidir}/test-requirements.txt + +commands= +{{^asyncio}} + nosetests \ + [] +{{/asyncio}} +{{#asyncio}} + pytest -v --cov petstore_api +{{/asyncio}} diff --git a/src/main/resources/mustache/python/travis.mustache b/src/main/resources/mustache/python/travis.mustache new file mode 100644 index 0000000000..86211e2d4a --- /dev/null +++ b/src/main/resources/mustache/python/travis.mustache @@ -0,0 +1,14 @@ +# ref: https://docs.travis-ci.com/user/languages/python +language: python +python: + - "2.7" + - "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