diff --git a/modules/swagger-codegen/src/main/java/io/swagger/codegen/CodegenConstants.java b/modules/swagger-codegen/src/main/java/io/swagger/codegen/CodegenConstants.java index d9bf80c1b62..4dea75e8208 100644 --- a/modules/swagger-codegen/src/main/java/io/swagger/codegen/CodegenConstants.java +++ b/modules/swagger-codegen/src/main/java/io/swagger/codegen/CodegenConstants.java @@ -96,6 +96,7 @@ public class CodegenConstants { public static final String ENSURE_UNIQUE_PARAMS = "ensureUniqueParams"; public static final String ENSURE_UNIQUE_PARAMS_DESC = "Whether to ensure parameter names are unique in an operation (rename parameters that are not)."; + public static final String PROJECT_NAME = "projectName"; public static final String PACKAGE_NAME = "packageName"; public static final String PACKAGE_VERSION = "packageVersion"; diff --git a/modules/swagger-codegen/src/main/java/io/swagger/codegen/languages/PythonClientCodegen.java b/modules/swagger-codegen/src/main/java/io/swagger/codegen/languages/PythonClientCodegen.java index a60bbcec16b..1dd62fdf189 100755 --- a/modules/swagger-codegen/src/main/java/io/swagger/codegen/languages/PythonClientCodegen.java +++ b/modules/swagger-codegen/src/main/java/io/swagger/codegen/languages/PythonClientCodegen.java @@ -1,649 +1,664 @@ -package io.swagger.codegen.languages; - -import io.swagger.codegen.CliOption; -import io.swagger.codegen.CodegenConfig; -import io.swagger.codegen.CodegenConstants; -import io.swagger.codegen.CodegenModel; -import io.swagger.codegen.CodegenParameter; -import io.swagger.codegen.CodegenProperty; -import io.swagger.codegen.CodegenType; -import io.swagger.codegen.DefaultCodegen; -import io.swagger.codegen.SupportingFile; -import io.swagger.models.properties.*; - -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 org.apache.commons.lang3.StringUtils; - -public class PythonClientCodegen extends DefaultCodegen implements CodegenConfig { - public static final String PACKAGE_URL = "packageUrl"; - - protected String packageName; - protected String packageVersion; - 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(); - - 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"); - - embeddedTemplateDir = templateDir = "python"; - - modelDocTemplateFiles.put("model_doc.mustache", ".md"); - apiDocTemplateFiles.put("api_doc.mustache", ".md"); - - testFolder = "test"; - - languageSpecificPrimitives.clear(); - languageSpecificPrimitives.add("int"); - languageSpecificPrimitives.add("float"); - languageSpecificPrimitives.add("list"); - languageSpecificPrimitives.add("bool"); - languageSpecificPrimitives.add("str"); - languageSpecificPrimitives.add("datetime"); - languageSpecificPrimitives.add("date"); - languageSpecificPrimitives.add("object"); - - typeMapping.clear(); - typeMapping.put("integer", "int"); - typeMapping.put("float", "float"); - typeMapping.put("number", "float"); - typeMapping.put("long", "int"); - typeMapping.put("double", "float"); - typeMapping.put("array", "list"); - typeMapping.put("map", "dict"); - typeMapping.put("boolean", "bool"); - typeMapping.put("string", "str"); - typeMapping.put("date", "date"); - typeMapping.put("DateTime", "datetime"); - typeMapping.put("object", "object"); - typeMapping.put("file", "file"); - // 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/release/2.5.4/ref/keywords.html - 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", "None")); - - 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.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, "hides the timestamp when files were generated") - .defaultValue(Boolean.TRUE.toString())); - } - - @Override - public void processOpts() { - super.processOpts(); - Boolean excludeTests = false; - - 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.PACKAGE_VERSION)) { - setPackageVersion((String) additionalProperties.get(CodegenConstants.PACKAGE_VERSION)); - } - else { - setPackageVersion("1.0.0"); - } - - // default HIDE_GENERATION_TIMESTAMP to true - if (!additionalProperties.containsKey(CodegenConstants.HIDE_GENERATION_TIMESTAMP)) { - additionalProperties.put(CodegenConstants.HIDE_GENERATION_TIMESTAMP, Boolean.TRUE.toString()); - } else { - additionalProperties.put(CodegenConstants.HIDE_GENERATION_TIMESTAMP, - Boolean.valueOf(additionalProperties().get(CodegenConstants.HIDE_GENERATION_TIMESTAMP).toString())); - } - - 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)); - } - - String swaggerFolder = packageName; - - modelPackage = swaggerFolder + File.separatorChar + "models"; - apiPackage = swaggerFolder + File.separatorChar + "apis"; - - supportingFiles.add(new SupportingFile("README.mustache", "", "README.md")); - - supportingFiles.add(new SupportingFile("setup.mustache", "", "setup.py")); - supportingFiles.add(new SupportingFile("tox.mustache", "", "tox.ini")); - supportingFiles.add(new SupportingFile("test-requirements.mustache", "", "test-requirements.txt")); - supportingFiles.add(new SupportingFile("requirements.mustache", "", "requirements.txt")); - - supportingFiles.add(new SupportingFile("api_client.mustache", swaggerFolder, "api_client.py")); - supportingFiles.add(new SupportingFile("rest.mustache", swaggerFolder, "rest.py")); - supportingFiles.add(new SupportingFile("configuration.mustache", swaggerFolder, "configuration.py")); - supportingFiles.add(new SupportingFile("__init__package.mustache", swaggerFolder, "__init__.py")); - supportingFiles.add(new SupportingFile("__init__model.mustache", modelPackage, "__init__.py")); - supportingFiles.add(new SupportingFile("__init__api.mustache", 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")); - } - - private static String dropDots(String str) { - return str.replaceAll("\\.", "_"); - } - - - @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 getTypeDeclaration(Property p) { - if (p instanceof ArrayProperty) { - ArrayProperty ap = (ArrayProperty) p; - Property inner = ap.getItems(); - return getSwaggerType(p) + "[" + getTypeDeclaration(inner) + "]"; - } else if (p instanceof MapProperty) { - MapProperty mp = (MapProperty) p; - Property inner = mp.getAdditionalProperties(); - - return getSwaggerType(p) + "(str, " + getTypeDeclaration(inner) + ")"; - } - return super.getTypeDeclaration(p); - } - - @Override - public String getSwaggerType(Property p) { - String swaggerType = super.getSwaggerType(p); - String type = null; - if (typeMapping.containsKey(swaggerType)) { - type = typeMapping.get(swaggerType); - if (languageSpecificPrimitives.contains(type)) { - return type; - } - } else { - type = toModelName(swaggerType); - } - return type; - } - - @Override - public 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) { - 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 filename. Renamed to " + underscore(dropDots("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 " + underscore("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; - } - - // underscore the model file name - // PhoneNumber => phone_number - return underscore(dropDots(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.rb => phone_number_api.rb - 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 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 p Swagger property object - * @return string presentation of the default value of the property - */ - @Override - public String toDefaultValue(Property p) { - if (p instanceof StringProperty) { - StringProperty dp = (StringProperty) p; - if (dp.getDefault() != null) { - if (Pattern.compile("\r\n|\r|\n").matcher(dp.getDefault()).find()) - return "'''" + dp.getDefault() + "'''"; - else - return "'" + dp.getDefault() + "'"; - } - } else if (p instanceof BooleanProperty) { - BooleanProperty dp = (BooleanProperty) p; - if (dp.getDefault() != null) { - if (dp.getDefault().toString().equalsIgnoreCase("false")) - return "False"; - else - return "True"; - } - } else if (p instanceof DateProperty) { - // TODO - } else if (p instanceof DateTimeProperty) { - // TODO - } else if (p instanceof DoubleProperty) { - DoubleProperty dp = (DoubleProperty) p; - if (dp.getDefault() != null) { - return dp.getDefault().toString(); - } - } else if (p instanceof FloatProperty) { - FloatProperty dp = (FloatProperty) p; - if (dp.getDefault() != null) { - return dp.getDefault().toString(); - } - } else if (p instanceof IntegerProperty) { - IntegerProperty dp = (IntegerProperty) p; - if (dp.getDefault() != null) { - return dp.getDefault().toString(); - } - } else if (p instanceof LongProperty) { - LongProperty dp = (LongProperty) p; - if (dp.getDefault() != null) { - return dp.getDefault().toString(); - } - } - - return null; - } - - @Override - public void setParameterExampleValue(CodegenParameter p) { - String example; - - if (p.defaultValue == null) { - example = p.example; - } else { - example = p.defaultValue; - } - - String type = p.baseType; - if (type == null) { - type = p.dataType; - } - - if ("String".equalsIgnoreCase(type) || "str".equalsIgnoreCase(type)) { - if (example == null) { - example = p.paramName + "_example"; - } - example = "'" + escapeText(example) + "'"; - } else if ("Integer".equals(type) || "int".equals(type)) { - if (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 (Boolean.TRUE.equals(p.isListContainer)) { - example = "[" + example + "]"; - } else if (Boolean.TRUE.equals(p.isMapContainer)) { - example = "{'key': " + example + "}"; - } - - p.example = example; - } - - @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("'''", "'_'_'"); - } - -} +package io.swagger.codegen.languages; + +import io.swagger.codegen.CliOption; +import io.swagger.codegen.CodegenConfig; +import io.swagger.codegen.CodegenConstants; +import io.swagger.codegen.CodegenModel; +import io.swagger.codegen.CodegenParameter; +import io.swagger.codegen.CodegenProperty; +import io.swagger.codegen.CodegenType; +import io.swagger.codegen.DefaultCodegen; +import io.swagger.codegen.SupportingFile; +import io.swagger.models.properties.*; + +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 org.apache.commons.lang3.StringUtils; + +public class PythonClientCodegen extends DefaultCodegen implements CodegenConfig { + public static final String PACKAGE_URL = "packageUrl"; + + 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(); + + 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"); + + embeddedTemplateDir = templateDir = "python"; + + modelDocTemplateFiles.put("model_doc.mustache", ".md"); + apiDocTemplateFiles.put("api_doc.mustache", ".md"); + + testFolder = "test"; + + languageSpecificPrimitives.clear(); + languageSpecificPrimitives.add("int"); + languageSpecificPrimitives.add("float"); + languageSpecificPrimitives.add("list"); + languageSpecificPrimitives.add("bool"); + languageSpecificPrimitives.add("str"); + languageSpecificPrimitives.add("datetime"); + languageSpecificPrimitives.add("date"); + languageSpecificPrimitives.add("object"); + + typeMapping.clear(); + typeMapping.put("integer", "int"); + typeMapping.put("float", "float"); + typeMapping.put("number", "float"); + typeMapping.put("long", "int"); + typeMapping.put("double", "float"); + typeMapping.put("array", "list"); + typeMapping.put("map", "dict"); + typeMapping.put("boolean", "bool"); + typeMapping.put("string", "str"); + typeMapping.put("date", "date"); + typeMapping.put("DateTime", "datetime"); + typeMapping.put("object", "object"); + typeMapping.put("file", "file"); + // 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/release/2.5.4/ref/keywords.html + 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", "None")); + + 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, "hides the timestamp when files were generated") + .defaultValue(Boolean.TRUE.toString())); + } + + @Override + public void processOpts() { + super.processOpts(); + Boolean excludeTests = false; + + 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"); + } + + // default HIDE_GENERATION_TIMESTAMP to true + if (!additionalProperties.containsKey(CodegenConstants.HIDE_GENERATION_TIMESTAMP)) { + additionalProperties.put(CodegenConstants.HIDE_GENERATION_TIMESTAMP, Boolean.TRUE.toString()); + } else { + additionalProperties.put(CodegenConstants.HIDE_GENERATION_TIMESTAMP, + Boolean.valueOf(additionalProperties().get(CodegenConstants.HIDE_GENERATION_TIMESTAMP).toString())); + } + + 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)); + } + + String swaggerFolder = packageName; + + modelPackage = swaggerFolder + File.separatorChar + "models"; + apiPackage = swaggerFolder + File.separatorChar + "apis"; + + supportingFiles.add(new SupportingFile("README.mustache", "", "README.md")); + + supportingFiles.add(new SupportingFile("setup.mustache", "", "setup.py")); + supportingFiles.add(new SupportingFile("tox.mustache", "", "tox.ini")); + supportingFiles.add(new SupportingFile("test-requirements.mustache", "", "test-requirements.txt")); + supportingFiles.add(new SupportingFile("requirements.mustache", "", "requirements.txt")); + + supportingFiles.add(new SupportingFile("api_client.mustache", swaggerFolder, "api_client.py")); + supportingFiles.add(new SupportingFile("rest.mustache", swaggerFolder, "rest.py")); + supportingFiles.add(new SupportingFile("configuration.mustache", swaggerFolder, "configuration.py")); + supportingFiles.add(new SupportingFile("__init__package.mustache", swaggerFolder, "__init__.py")); + supportingFiles.add(new SupportingFile("__init__model.mustache", modelPackage, "__init__.py")); + supportingFiles.add(new SupportingFile("__init__api.mustache", 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")); + } + + private static String dropDots(String str) { + return str.replaceAll("\\.", "_"); + } + + @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 getTypeDeclaration(Property p) { + if (p instanceof ArrayProperty) { + ArrayProperty ap = (ArrayProperty) p; + Property inner = ap.getItems(); + return getSwaggerType(p) + "[" + getTypeDeclaration(inner) + "]"; + } else if (p instanceof MapProperty) { + MapProperty mp = (MapProperty) p; + Property inner = mp.getAdditionalProperties(); + + return getSwaggerType(p) + "(str, " + getTypeDeclaration(inner) + ")"; + } + return super.getTypeDeclaration(p); + } + + @Override + public String getSwaggerType(Property p) { + String swaggerType = super.getSwaggerType(p); + String type = null; + if (typeMapping.containsKey(swaggerType)) { + type = typeMapping.get(swaggerType); + if (languageSpecificPrimitives.contains(type)) { + return type; + } + } else { + type = toModelName(swaggerType); + } + return type; + } + + @Override + public 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) { + 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 filename. Renamed to " + underscore(dropDots("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 " + underscore("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; + } + + // underscore the model file name + // PhoneNumber => phone_number + return underscore(dropDots(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.rb => phone_number_api.rb + 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 p Swagger property object + * @return string presentation of the default value of the property + */ + @Override + public String toDefaultValue(Property p) { + if (p instanceof StringProperty) { + StringProperty dp = (StringProperty) p; + if (dp.getDefault() != null) { + if (Pattern.compile("\r\n|\r|\n").matcher(dp.getDefault()).find()) + return "'''" + dp.getDefault() + "'''"; + else + return "'" + dp.getDefault() + "'"; + } + } else if (p instanceof BooleanProperty) { + BooleanProperty dp = (BooleanProperty) p; + if (dp.getDefault() != null) { + if (dp.getDefault().toString().equalsIgnoreCase("false")) + return "False"; + else + return "True"; + } + } else if (p instanceof DateProperty) { + // TODO + } else if (p instanceof DateTimeProperty) { + // TODO + } else if (p instanceof DoubleProperty) { + DoubleProperty dp = (DoubleProperty) p; + if (dp.getDefault() != null) { + return dp.getDefault().toString(); + } + } else if (p instanceof FloatProperty) { + FloatProperty dp = (FloatProperty) p; + if (dp.getDefault() != null) { + return dp.getDefault().toString(); + } + } else if (p instanceof IntegerProperty) { + IntegerProperty dp = (IntegerProperty) p; + if (dp.getDefault() != null) { + return dp.getDefault().toString(); + } + } else if (p instanceof LongProperty) { + LongProperty dp = (LongProperty) p; + if (dp.getDefault() != null) { + return dp.getDefault().toString(); + } + } + + return null; + } + + @Override + public void setParameterExampleValue(CodegenParameter p) { + String example; + + if (p.defaultValue == null) { + example = p.example; + } else { + example = p.defaultValue; + } + + String type = p.baseType; + if (type == null) { + type = p.dataType; + } + + if ("String".equalsIgnoreCase(type) || "str".equalsIgnoreCase(type)) { + if (example == null) { + example = p.paramName + "_example"; + } + example = "'" + escapeText(example) + "'"; + } else if ("Integer".equals(type) || "int".equals(type)) { + if (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 (Boolean.TRUE.equals(p.isListContainer)) { + example = "[" + example + "]"; + } else if (Boolean.TRUE.equals(p.isMapContainer)) { + example = "{'key': " + example + "}"; + } + + p.example = example; + } + + @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/modules/swagger-codegen/src/main/resources/python/README.mustache b/modules/swagger-codegen/src/main/resources/python/README.mustache index 9f432447443..c1355be7784 100644 --- a/modules/swagger-codegen/src/main/resources/python/README.mustache +++ b/modules/swagger-codegen/src/main/resources/python/README.mustache @@ -1,4 +1,4 @@ -# {{packageName}} +# {{{projectName}}} {{#appDescription}} {{{appDescription}}} {{/appDescription}} diff --git a/modules/swagger-codegen/src/main/resources/python/setup.mustache b/modules/swagger-codegen/src/main/resources/python/setup.mustache index 6a1a4320f38..6fc7f198d15 100644 --- a/modules/swagger-codegen/src/main/resources/python/setup.mustache +++ b/modules/swagger-codegen/src/main/resources/python/setup.mustache @@ -5,7 +5,7 @@ import sys from setuptools import setup, find_packages -NAME = "{{packageName}}" +NAME = "{{{projectName}}}" VERSION = "{{packageVersion}}" {{#apiInfo}} {{#apis}} diff --git a/modules/swagger-codegen/src/test/java/io/swagger/codegen/options/PythonClientOptionsProvider.java b/modules/swagger-codegen/src/test/java/io/swagger/codegen/options/PythonClientOptionsProvider.java index 7ad1a05752a..262c312e563 100644 --- a/modules/swagger-codegen/src/test/java/io/swagger/codegen/options/PythonClientOptionsProvider.java +++ b/modules/swagger-codegen/src/test/java/io/swagger/codegen/options/PythonClientOptionsProvider.java @@ -9,6 +9,7 @@ import java.util.Map; public class PythonClientOptionsProvider implements OptionsProvider { public static final String PACKAGE_NAME_VALUE = "swagger_client_python"; + public static final String PROJECT_NAME_VALUE = "swagger-client-python"; public static final String PACKAGE_VERSION_VALUE = "1.0.0-SNAPSHOT"; public static final String PACKAGE_URL_VALUE = ""; @@ -22,6 +23,7 @@ public class PythonClientOptionsProvider implements OptionsProvider { ImmutableMap.Builder builder = new ImmutableMap.Builder(); return builder.put(PythonClientCodegen.PACKAGE_URL, PACKAGE_URL_VALUE) .put(CodegenConstants.PACKAGE_NAME, PACKAGE_NAME_VALUE) + .put(CodegenConstants.PROJECT_NAME, PROJECT_NAME_VALUE) .put(CodegenConstants.PACKAGE_VERSION, PACKAGE_VERSION_VALUE) .put(CodegenConstants.SORT_PARAMS_BY_REQUIRED_FLAG, "true") .put(CodegenConstants.HIDE_GENERATION_TIMESTAMP, "true") diff --git a/modules/swagger-codegen/src/test/java/io/swagger/codegen/python/PythonClientOptionsTest.java b/modules/swagger-codegen/src/test/java/io/swagger/codegen/python/PythonClientOptionsTest.java index 80fc53ac993..8529ea0c97d 100644 --- a/modules/swagger-codegen/src/test/java/io/swagger/codegen/python/PythonClientOptionsTest.java +++ b/modules/swagger-codegen/src/test/java/io/swagger/codegen/python/PythonClientOptionsTest.java @@ -28,6 +28,8 @@ public class PythonClientOptionsTest extends AbstractOptionsTest { new Expectations(clientCodegen) {{ clientCodegen.setPackageName(PythonClientOptionsProvider.PACKAGE_NAME_VALUE); times = 1; + clientCodegen.setProjectName(PythonClientOptionsProvider.PROJECT_NAME_VALUE); + times = 1; clientCodegen.setPackageVersion(PythonClientOptionsProvider.PACKAGE_VERSION_VALUE); times = 1; clientCodegen.setPackageUrl(PythonClientOptionsProvider.PACKAGE_URL_VALUE); diff --git a/samples/client/petstore/python/README.md b/samples/client/petstore/python/README.md index f10eb1d595e..8559584e01b 100644 --- a/samples/client/petstore/python/README.md +++ b/samples/client/petstore/python/README.md @@ -1,4 +1,4 @@ -# petstore_api +# petstore-api This spec is mainly for testing Petstore server and contains fake endpoints, models. Please do not use this for any other purpose. Special characters: \" \\ This Python package is automatically generated by the [Swagger Codegen](https://github.com/swagger-api/swagger-codegen) project: diff --git a/samples/client/petstore/python/setup.py b/samples/client/petstore/python/setup.py index 70fc36e144d..187e1b1636f 100644 --- a/samples/client/petstore/python/setup.py +++ b/samples/client/petstore/python/setup.py @@ -14,7 +14,7 @@ import sys from setuptools import setup, find_packages -NAME = "petstore_api" +NAME = "petstore-api" VERSION = "1.0.0" # To install the library, run the following #