diff --git a/generate-code.py b/generate-code.py index b68fd89ed..5a44cc20d 100644 --- a/generate-code.py +++ b/generate-code.py @@ -3,16 +3,19 @@ import sys def run_command(command): + print(command) proc = subprocess.run(command, shell=True, text=True, capture_output=True) + if len(proc.stdout) != 0: + print("\n\nSTDOUT:\n\n") + print(proc.stdout) + if len(proc.stderr) != 0: print("\n\nSTDERR:\n\n") print(proc.stderr) print("\n\n") if proc.returncode != 0: - print("\n\nSTDOUT:\n\n") - print(proc.stdout) print(f"\n\nCommand '{command}' returned non-zero exit status {proc.returncode}.") sys.exit(1) @@ -138,9 +141,10 @@ def main(): run_command(f'rm -rf {modelPackagePath}/') command = f'''java \\ - -cp ./tools/openapi-generator-cli.jar:./generator/target/python-nextgen-custom-client-openapi-generator-1.0.0.jar \\ + -cp ./generator/target/python-nextgen-custom-client-openapi-generator-1.0.0.jar \\ org.openapitools.codegen.OpenAPIGenerator \\ generate \\ + -e pebble \\ -g python-nextgen-custom-client \\ -o . \\ --global-property modelDocs=false \\ @@ -160,9 +164,10 @@ def main(): run_command(f'rm -rf {modelPackagePath}/') command = f'''java \\ - -cp ./tools/openapi-generator-cli.jar:./generator/target/python-nextgen-custom-client-openapi-generator-1.0.0.jar \\ + -cp ./generator/target/python-nextgen-custom-client-openapi-generator-1.0.0.jar \\ org.openapitools.codegen.OpenAPIGenerator \\ generate \\ + -e pebble \\ -g python-nextgen-custom-client \\ -o . \\ --global-property modelDocs=false,apiDocs=false \\ diff --git a/generator/.gitignore b/generator/.gitignore new file mode 100644 index 000000000..d85232be7 --- /dev/null +++ b/generator/.gitignore @@ -0,0 +1,4 @@ +target/ +.vscode/ +/out/ +/dependency-reduced-pom.xml diff --git a/generator/pom.xml b/generator/pom.xml index 8b547335f..8970af545 100644 --- a/generator/pom.xml +++ b/generator/pom.xml @@ -45,16 +45,16 @@ - org.apache.maven.plugins maven-jar-plugin 3.5.0 - - test-jar - + default-jar + + jar + @@ -75,7 +75,8 @@ - src/main/java + src/main/java + @@ -88,7 +89,8 @@ - src/test/java + src/test/java + @@ -103,6 +105,19 @@ 1.8 + + org.apache.maven.plugins + maven-shade-plugin + 3.6.2 + + + package + + shade + + + + @@ -113,20 +128,31 @@ provided - junit - junit - ${junit-version} + org.openapitools + openapi-generator-cli + ${openapi-generator-version} + + + io.pebbletemplates + pebble + 4.1.2 + + + org.junit.jupiter + junit-jupiter-api + 6.1.0 + test - ch.qos.logback - logback-classic - 1.5.34 + org.junit.jupiter + junit-jupiter-engine + 6.1.0 + test UTF-8 7.23.0 1.0.0 - 4.13.2 diff --git a/generator/src/main/java/line/bot/generator/PythonNextgenCustomClientGenerator.java b/generator/src/main/java/line/bot/generator/PythonNextgenCustomClientGenerator.java index b5a4f0409..79044ea7c 100644 --- a/generator/src/main/java/line/bot/generator/PythonNextgenCustomClientGenerator.java +++ b/generator/src/main/java/line/bot/generator/PythonNextgenCustomClientGenerator.java @@ -13,141 +13,56 @@ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. - * - * This code is almost same as https://github.com/OpenAPITools/openapi-generator/blob/61aadb32bdf9fc9095d9006d1f7d38a72617b847/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/PythonNextgenClientCodegen.java. - * mustache files are in https://github.com/OpenAPITools/openapi-generator/tree/61aadb32bdf9fc9095d9006d1f7d38a72617b847/modules/openapi-generator/src/main/resources/python-nextgen. */ package line.bot.generator; -import io.swagger.v3.oas.models.media.ArraySchema; -import io.swagger.v3.oas.models.media.Schema; -import io.swagger.v3.oas.models.security.SecurityScheme; import org.apache.commons.lang3.StringUtils; import org.openapitools.codegen.*; -import org.openapitools.codegen.languages.AbstractPythonCodegen; -import org.openapitools.codegen.meta.GeneratorMetadata; -import org.openapitools.codegen.meta.Stability; -import org.openapitools.codegen.meta.features.*; -import org.openapitools.codegen.meta.features.*; +import org.openapitools.codegen.languages.PythonPydanticV1ClientCodegen; import org.openapitools.codegen.model.ModelMap; import org.openapitools.codegen.model.ModelsMap; import org.openapitools.codegen.model.OperationMap; import org.openapitools.codegen.model.OperationsMap; import org.openapitools.codegen.utils.ModelUtils; -import org.openapitools.codegen.utils.ProcessUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.io.File; import java.util.*; -import static org.openapitools.codegen.utils.StringUtils.escape; -import static org.openapitools.codegen.utils.StringUtils.underscore; import static org.openapitools.codegen.utils.StringUtils.camelize; +import static org.openapitools.codegen.utils.StringUtils.underscore; -public class PythonNextgenCustomClientGenerator extends AbstractPythonCodegen implements CodegenConfig { +public class PythonNextgenCustomClientGenerator extends PythonPydanticV1ClientCodegen implements CodegenConfig { private final Logger LOGGER = LoggerFactory.getLogger(PythonNextgenCustomClientGenerator.class); - public static final String PACKAGE_URL = "packageUrl"; - public static final String DEFAULT_LIBRARY = "urllib3"; - public static final String RECURSION_LIMIT = "recursionLimit"; - public static final String DATETIME_FORMAT = "datetimeFormat"; - public static final String DATE_FORMAT = "dateFormat"; - public static final String MAP_NUMBER_TO = "mapNumberTo"; - - protected String packageUrl; - protected String apiDocPath = "docs" + File.separator; - protected String modelDocPath = "docs" + File.separator; - protected boolean hasModelsToImport = Boolean.FALSE; - protected boolean useOneOfDiscriminatorLookup = false; // use oneOf discriminator's mapping for model lookup - protected String datetimeFormat = "%Y-%m-%dT%H:%M:%S.%f%z"; - protected String dateFormat = "%Y-%m-%d"; - protected String mapNumberTo = "Union[StrictFloat, StrictInt]"; - - protected Map regexModifiers; - - private String testFolder; - - // map of set (model imports) - private HashMap> circularImports = new HashMap<>(); - // map of codegen models - private HashMap codegenModelMap = new HashMap<>(); + // Upstream codegenModelMap / circularImports are private, so we keep our own copies. + private final HashMap> lineCircularImports = new HashMap<>(); + private final HashMap lineCodegenModelMap = new HashMap<>(); public PythonNextgenCustomClientGenerator() { super(); - // force sortParamsByRequiredFlag to true to make the api method signature less complicated - sortParamsByRequiredFlag = true; - - modifyFeatureSet(features -> features - .includeDocumentationFeatures(DocumentationFeature.Readme) - .wireFormatFeatures(EnumSet.of(WireFormatFeature.JSON, WireFormatFeature.XML, WireFormatFeature.Custom)) - .securityFeatures(EnumSet.of( - SecurityFeature.BasicAuth, - SecurityFeature.BearerToken, - SecurityFeature.ApiKey, - SecurityFeature.OAuth2_Implicit - )) - .excludeGlobalFeatures( - GlobalFeature.XMLStructureDefinitions, - GlobalFeature.Callbacks, - GlobalFeature.LinkObjects, - GlobalFeature.ParameterStyling - ) - .includeSchemaSupportFeatures( - SchemaSupportFeature.Polymorphism, - SchemaSupportFeature.allOf, - SchemaSupportFeature.oneOf, - SchemaSupportFeature.anyOf - ) - .excludeParameterFeatures( - ParameterFeature.Cookie - ) - ); - - generatorMetadata = GeneratorMetadata.newBuilder(generatorMetadata) - .stability(Stability.STABLE) - .build(); - - // clear import mapping (from default generator) as python does not use it - // at the moment - importMapping.clear(); - - // override type mapping in abstract python codegen - typeMapping.put("array", "List"); - typeMapping.put("set", "List"); - typeMapping.put("map", "Dict"); - typeMapping.put("decimal", "decimal.Decimal"); - typeMapping.put("file", "bytearray"); - typeMapping.put("binary", "bytearray"); - typeMapping.put("ByteArray", "bytearray"); - - languageSpecificPrimitives.remove("file"); - languageSpecificPrimitives.add("decimal.Decimal"); - languageSpecificPrimitives.add("bytearray"); - languageSpecificPrimitives.add("none_type"); - - supportsInheritance = true; - modelPackage = "models"; - apiPackage = "api"; - outputFolder = "generated-code" + File.separatorChar + "python"; - - modelTemplateFiles.put("model.mustache", ".py"); - apiTemplateFiles.put("api.mustache", ".py"); - apiTemplateFiles.put("asyncio/async_api.mustache", ".py"); - embeddedTemplateDir = templateDir = "python-nextgen-custom-client"; - modelDocTemplateFiles.put("model_doc.mustache", ".md"); - apiDocTemplateFiles.put("api_doc.mustache", ".md"); + // Replace upstream .mustache templates with .pebble; emit both sync and async APIs. + apiTemplateFiles.clear(); + apiTemplateFiles.put("api.pebble", ".py"); + apiTemplateFiles.put("asyncio/async_api.pebble", ".py"); - testFolder = "test"; + modelTemplateFiles.clear(); + modelTemplateFiles.put("model.pebble", ".py"); - // default HIDE_GENERATION_TIMESTAMP to true - hideGenerationTimestamp = Boolean.TRUE; + // Model docs are always disabled via --global-property modelDocs=false. + modelDocTemplateFiles.clear(); - // from https://docs.python.org/3/reference/lexical_analysis.html#keywords + apiDocTemplateFiles.clear(); + apiDocTemplateFiles.put("api_doc.pebble", ".md"); + + modelTestTemplateFiles.clear(); + apiTestTemplateFiles.clear(); + + // Reserve pydantic keywords (schema/base64/json/date) on top of the Python reserved words. setReservedWordsLowerCase( Arrays.asList( // pydantic keyword @@ -161,57 +76,9 @@ public PythonNextgenCustomClientGenerator() { "print", "class", "exec", "in", "raise", "continue", "finally", "is", "return", "def", "for", "lambda", "try", "self", "nonlocal", "None", "True", "False", "async", "await")); - - 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("openapi_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(new CliOption(CodegenConstants.HIDE_GENERATION_TIMESTAMP, CodegenConstants.HIDE_GENERATION_TIMESTAMP_DESC) - .defaultValue(Boolean.TRUE.toString())); - cliOptions.add(new CliOption(CodegenConstants.SOURCECODEONLY_GENERATION, CodegenConstants.SOURCECODEONLY_GENERATION_DESC) - .defaultValue(Boolean.FALSE.toString())); - cliOptions.add(new CliOption(RECURSION_LIMIT, "Set the recursion limit. If not set, use the system default value.")); - cliOptions.add(new CliOption(MAP_NUMBER_TO, "Map number to Union[StrictFloat, StrictInt], StrictStr or float.") - .defaultValue("Union[StrictFloat, StrictInt]")); - cliOptions.add(new CliOption(DATETIME_FORMAT, "datetime format for query parameters") - .defaultValue("%Y-%m-%dT%H:%M:%S%z")); - cliOptions.add(new CliOption(DATE_FORMAT, "date format for query parameters") - .defaultValue("%Y-%m-%d")); - - supportedLibraries.put("urllib3", "urllib3-based client"); - supportedLibraries.put("asyncio", "asyncio-based client"); - supportedLibraries.put("tornado", "tornado-based client (deprecated)"); - CliOption libraryOption = new CliOption(CodegenConstants.LIBRARY, "library template (sub-template) to use: asyncio, tornado (deprecated), urllib3"); - libraryOption.setDefault(DEFAULT_LIBRARY); - cliOptions.add(libraryOption); - setLibrary(DEFAULT_LIBRARY); - - // option to change how we process + set the data in the 'additionalProperties' keyword. - CliOption disallowAdditionalPropertiesIfNotPresentOpt = CliOption.newBoolean( - CodegenConstants.DISALLOW_ADDITIONAL_PROPERTIES_IF_NOT_PRESENT, - CodegenConstants.DISALLOW_ADDITIONAL_PROPERTIES_IF_NOT_PRESENT_DESC).defaultValue(Boolean.TRUE.toString()); - Map disallowAdditionalPropertiesIfNotPresentOpts = new HashMap<>(); - disallowAdditionalPropertiesIfNotPresentOpts.put("false", - "The 'additionalProperties' implementation is compliant with the OAS and JSON schema specifications."); - disallowAdditionalPropertiesIfNotPresentOpts.put("true", - "Keep the old (incorrect) behaviour that 'additionalProperties' is set to false by default."); - disallowAdditionalPropertiesIfNotPresentOpt.setEnum(disallowAdditionalPropertiesIfNotPresentOpts); - cliOptions.add(disallowAdditionalPropertiesIfNotPresentOpt); - this.setDisallowAdditionalPropertiesIfNotPresent(true); } - // Do not generate /.*AllOf.py./ + // Suppress AllOf.py models by disabling the upstream inline model resolver. @Override public boolean getUseInlineModelResolver() { return false; @@ -219,295 +86,541 @@ public boolean getUseInlineModelResolver() { @Override public void processOpts() { - this.setLegacyDiscriminatorBehavior(false); + // Default excludeTests=true so the test __init__.py is not registered by super. + if (!additionalProperties.containsKey(CodegenConstants.EXCLUDE_TESTS)) { + additionalProperties.put(CodegenConstants.EXCLUDE_TESTS, "true"); + } super.processOpts(); - // map to Dot instead of Period - specialCharReplacements.put(".", "Dot"); + // Re-register only the supporting files we actually emit (generate-code.py + // always runs with generateSourceCodeOnly=true, so setup.py / pyproject.toml / + // tox.ini / requirements / .gitignore etc. are intentionally skipped). + supportingFiles.clear(); - if (StringUtils.isEmpty(System.getenv("PYTHON_POST_PROCESS_FILE"))) { - LOGGER.info("Environment variable PYTHON_POST_PROCESS_FILE not defined so the Python code may not be properly formatted. To define it, try 'export PYTHON_POST_PROCESS_FILE=\"/usr/local/bin/yapf -i\"' (Linux/Mac)"); - LOGGER.info("NOTE: To enable file post-processing, 'enablePostProcessFile' must be set to `true` (--enable-post-process-file for CLI)."); - } + String modelPath = modelPackage.replace('.', java.io.File.separatorChar); + String apiPath = apiPackage.replace('.', java.io.File.separatorChar); - Boolean excludeTests = true; + supportingFiles.add(new SupportingFile("README_onlypackage.pebble", "", packagePath() + "_README.md")); + supportingFiles.add(new SupportingFile("configuration.pebble", packagePath(), "configuration.py")); + supportingFiles.add(new SupportingFile("__init__package.pebble", packagePath(), "__init__.py")); + supportingFiles.add(new SupportingFile("__init__model.pebble", modelPath, "__init__.py")); + supportingFiles.add(new SupportingFile("__init__api.pebble", apiPath, "__init__.py")); - if (additionalProperties.containsKey(CodegenConstants.PACKAGE_NAME)) { - setPackageName((String) additionalProperties.get(CodegenConstants.PACKAGE_NAME)); - } + // LINE OpenAPI has no HTTP signature scheme, so signing.py is not generated. - 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("_", "-")); + // package name with dots gets directory structure + String[] packageNameSplits = packageName.split("\\."); + String currentPackagePath = ""; + for (int i = 0; i < packageNameSplits.length - 1; i++) { + if (i > 0) { + currentPackagePath = currentPackagePath + java.io.File.separatorChar; + } + currentPackagePath = currentPackagePath + packageNameSplits[i]; + supportingFiles.add(new SupportingFile("__init__.pebble", currentPackagePath, "__init__.py")); } - if (additionalProperties.containsKey(CodegenConstants.PACKAGE_VERSION)) { - setPackageVersion((String) additionalProperties.get(CodegenConstants.PACKAGE_VERSION)); + supportingFiles.add(new SupportingFile("exceptions.pebble", packagePath(), "exceptions.py")); + + Boolean excludeTests = Boolean.valueOf(additionalProperties.get(CodegenConstants.EXCLUDE_TESTS).toString()); + if (Boolean.FALSE.equals(excludeTests)) { + supportingFiles.add(new SupportingFile("__init__.pebble", "test", "__init__.py")); } - additionalProperties.put(CodegenConstants.PROJECT_NAME, projectName); - additionalProperties.put(CodegenConstants.PACKAGE_NAME, packageName); - additionalProperties.put(CodegenConstants.PACKAGE_VERSION, packageVersion); + // Emit sync and async api_client / REST in the same package. + supportingFiles.add(new SupportingFile("api_client.pebble", packagePath(), "api_client.py")); + supportingFiles.add(new SupportingFile("asyncio/api_client.pebble", packagePath(), "async_api_client.py")); - if (additionalProperties.containsKey(CodegenConstants.EXCLUDE_TESTS)) { - excludeTests = Boolean.valueOf(additionalProperties.get(CodegenConstants.EXCLUDE_TESTS).toString()); - } + supportingFiles.add(new SupportingFile("api_response.pebble", packagePath(), "api_response.py")); - Boolean generateSourceCodeOnly = false; - if (additionalProperties.containsKey(CodegenConstants.SOURCECODEONLY_GENERATION)) { - generateSourceCodeOnly = Boolean.valueOf(additionalProperties.get(CodegenConstants.SOURCECODEONLY_GENERATION).toString()); - } + supportingFiles.add(new SupportingFile("rest.pebble", packagePath(), "rest.py")); + supportingFiles.add(new SupportingFile("asyncio/rest.pebble", packagePath(), "async_rest.py")); + } - if (generateSourceCodeOnly) { - // tests in /test - testFolder = packagePath() + File.separatorChar + testFolder; - // api/model docs in /docs - apiDocPath = packagePath() + File.separatorChar + apiDocPath; - modelDocPath = packagePath() + File.separatorChar + modelDocPath; - } - // make api and model doc path available in mustache template - additionalProperties.put("apiDocPath", apiDocPath); - additionalProperties.put("modelDocPath", modelDocPath); + @Override + public String getName() { + return "python-nextgen-custom-client"; + } - if (additionalProperties.containsKey(PACKAGE_URL)) { - setPackageUrl((String) additionalProperties.get(PACKAGE_URL)); - } + @Override + public String getHelp() { + return "Generates a Python client library."; + } - // check to see if setRecursionLimit is set and whether it's an integer - if (additionalProperties.containsKey(RECURSION_LIMIT)) { - try { - Integer.parseInt((String) additionalProperties.get(RECURSION_LIMIT)); - } catch (NumberFormatException | NullPointerException e) { - throw new IllegalArgumentException("recursionLimit must be an integer, e.g. 2000."); - } - } + @Override + public String generatorLanguageVersion() { + return "3.10+"; + } - if (additionalProperties.containsKey(CodegenConstants.USE_ONEOF_DISCRIMINATOR_LOOKUP)) { - setUseOneOfDiscriminatorLookup(convertPropertyToBooleanAndWriteBack(CodegenConstants.USE_ONEOF_DISCRIMINATOR_LOOKUP)); - } else { - additionalProperties.put(CodegenConstants.USE_ONEOF_DISCRIMINATOR_LOOKUP, useOneOfDiscriminatorLookup); - } + // Keep existing class / file names by skipping the upstream "Api" suffix. + @Override + public String toApiName(String name) { + return camelize(name); + } - if (additionalProperties.containsKey(MAP_NUMBER_TO)) { - setMapNumberTo(String.valueOf(additionalProperties.get(MAP_NUMBER_TO))); + // Files produced from asyncio templates get an "async_" prefix. + @Override + public String apiFilename(String templateName, String tag) { + String result = super.apiFilename(templateName, tag); + if (templateName.startsWith("async")) { + int ix = result.lastIndexOf('/'); + result = result.substring(0, ix + 1) + "async_" + result.substring(ix + 1); } + return result; + } - if (additionalProperties.containsKey(DATETIME_FORMAT)) { - setDatetimeFormat((String) additionalProperties.get(DATETIME_FORMAT)); - } else { - additionalProperties.put(DATETIME_FORMAT, datetimeFormat); - } + // Emit `from pydantic.v1 import ...` (instead of upstream's `from pydantic import ...`) + // and omit the typing_extensions / postponed import machinery the upstream adds. + @Override + public OperationsMap postProcessOperationsWithModels(OperationsMap objs, List allModels) { + hasModelsToImport = false; + TreeSet typingImports = new TreeSet<>(); + TreeSet pydanticImports = new TreeSet<>(); + TreeSet datetimeImports = new TreeSet<>(); + TreeSet modelImports = new TreeSet<>(); - if (additionalProperties.containsKey(DATE_FORMAT)) { - setDateFormat((String) additionalProperties.get(DATE_FORMAT)); - } else { - additionalProperties.put(DATE_FORMAT, dateFormat); + OperationMap objectMap = objs.getOperations(); + List operations = objectMap.getOperation(); + for (CodegenOperation operation : operations) { + TreeSet exampleImports = new TreeSet<>(); + List params = operation.allParams; + + for (CodegenParameter param : params) { + String typing = getPydanticType(param, typingImports, pydanticImports, datetimeImports, modelImports, exampleImports, null); + List fields = new ArrayList<>(); + String firstField = ""; + + if (!param.required) { + firstField = "None"; + typing = "Optional[" + typing + "]"; + typingImports.add("Optional"); + } else { + firstField = "..."; + if (param.isNullable) { + typing = "Optional[" + typing + "]"; + typingImports.add("Optional"); + } + } + + if (!StringUtils.isEmpty(param.description)) { + fields.add(String.format(Locale.ROOT, "description=\"%s\"", param.description)); + } + + String fieldCustomization; + if ("None".equals(firstField)) { + fieldCustomization = null; + } else { + fieldCustomization = firstField; + } + + if (!fields.isEmpty()) { + if (fieldCustomization != null) { + fields.add(0, fieldCustomization); + } + pydanticImports.add("Field"); + fieldCustomization = String.format(Locale.ROOT, "Field(%s)", StringUtils.join(fields, ", ")); + } else { + fieldCustomization = "Field()"; + } + + if ("Field()".equals(fieldCustomization)) { + param.vendorExtensions.put("x-py-typing", typing); + } else { + param.vendorExtensions.put("x-py-typing", String.format(Locale.ROOT, "Annotated[%s, %s]", typing, fieldCustomization)); + } + } + + if (!StringUtils.isEmpty(operation.returnType)) { + String typing = getPydanticType(operation.returnProperty, typingImports, + new TreeSet<>(), datetimeImports, modelImports, exampleImports, null); + } + + if (!exampleImports.isEmpty()) { + List imports = new ArrayList<>(); + for (String exampleImport : exampleImports) { + imports.add("from " + packageName + ".models." + underscore(exampleImport) + " import " + exampleImport); + } + operation.vendorExtensions.put("x-py-example-import", imports); + } } - String modelPath = packagePath() + File.separatorChar + modelPackage.replace('.', File.separatorChar); - String apiPath = packagePath() + File.separatorChar + apiPackage.replace('.', File.separatorChar); + List> newImports = new ArrayList<>(); - String readmePath = "README.md"; - String readmeTemplate = "README.mustache"; - if (generateSourceCodeOnly) { - readmePath = packagePath() + "_" + readmePath; - readmeTemplate = "README_onlypackage.mustache"; + if (!datetimeImports.isEmpty()) { + Map item = new HashMap<>(); + item.put("import", String.format(Locale.ROOT, "from datetime import %s\n", StringUtils.join(datetimeImports, ", "))); + newImports.add(item); } - supportingFiles.add(new SupportingFile(readmeTemplate, "", readmePath)); - - if (!generateSourceCodeOnly) { - 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("setup_cfg.mustache", "", "setup.cfg")); - - supportingFiles.add(new SupportingFile("gitignore.mustache", "", ".gitignore")); - supportingFiles.add(new SupportingFile("github-workflow.mustache", ".github/workflows", "python.yml")); - supportingFiles.add(new SupportingFile("setup.mustache", "", "setup.py")); - supportingFiles.add(new SupportingFile("pyproject.mustache", "", "pyproject.toml")); + + if (!pydanticImports.isEmpty()) { + Map item = new HashMap<>(); + item.put("import", String.format(Locale.ROOT, "from pydantic.v1 import %s\n", StringUtils.join(pydanticImports, ", "))); + newImports.add(item); } - supportingFiles.add(new SupportingFile("configuration.mustache", packagePath(), "configuration.py")); - supportingFiles.add(new SupportingFile("__init__package.mustache", packagePath(), "__init__.py")); - supportingFiles.add(new SupportingFile("__init__model.mustache", modelPath, "__init__.py")); - supportingFiles.add(new SupportingFile("__init__api.mustache", apiPath, "__init__.py")); - // Generate the 'signing.py' module, but only if the 'HTTP signature' security scheme is specified in the OAS. - Map securitySchemeMap = openAPI != null ? - (openAPI.getComponents() != null ? openAPI.getComponents().getSecuritySchemes() : null) : null; - List authMethods = fromSecurity(securitySchemeMap); - if (ProcessUtils.hasHttpSignatureMethods(authMethods)) { - supportingFiles.add(new SupportingFile("signing.mustache", packagePath(), "signing.py")); + + if (!typingImports.isEmpty()) { + Map item = new HashMap<>(); + item.put("import", String.format(Locale.ROOT, "from typing import %s\n", StringUtils.join(typingImports, ", "))); + newImports.add(item); } - // If the package name consists of dots(openapi.client), then we need to create the directory structure like openapi/client with __init__ files. - String[] packageNameSplits = packageName.split("\\."); - String currentPackagePath = ""; - for (int i = 0; i < packageNameSplits.length - 1; i++) { - if (i > 0) { - currentPackagePath = currentPackagePath + File.separatorChar; + if (!modelImports.isEmpty()) { + for (String modelImport : modelImports) { + Map item = new HashMap<>(); + item.put("import", "from " + packageName + ".models." + underscore(modelImport) + " import " + modelImport); + newImports.add(item); } - currentPackagePath = currentPackagePath + packageNameSplits[i]; - supportingFiles.add(new SupportingFile("__init__.mustache", currentPackagePath, "__init__.py")); } - supportingFiles.add(new SupportingFile("exceptions.mustache", packagePath(), "exceptions.py")); - - if (Boolean.FALSE.equals(excludeTests)) { - supportingFiles.add(new SupportingFile("__init__.mustache", testFolder, "__init__.py")); - } + objs.setImports(newImports); + return objs; + } - // Generate both sync and async client/rest files. - supportingFiles.add(new SupportingFile("api_client.mustache", packagePath(), "api_client.py")); - supportingFiles.add(new SupportingFile("asyncio/api_client.mustache", packagePath(), "async_api_client.py")); + // Re-run our own postProcessModelsMap to emit pydantic.v1 imports and the + // discriminator x-py-type-name extension (upstream's is private). + @Override + public Map postProcessAllModels(Map objs) { + final Map processed = super.postProcessAllModels(objs); - supportingFiles.add(new SupportingFile("api_response.mustache", packagePath(), "api_response.py")); + for (Map.Entry entry : objs.entrySet()) { + CodegenModel cm = ModelUtils.getModelByName(entry.getKey(), objs); + lineCodegenModelMap.put(cm.classname, ModelUtils.getModelByName(entry.getKey(), objs)); + } - supportingFiles.add(new SupportingFile("rest.mustache", packagePath(), "rest.py")); - supportingFiles.add(new SupportingFile("asyncio/rest.mustache", packagePath(), "async_rest.py")); + for (String m : lineCodegenModelMap.keySet()) { + createLineImportMapOfSet(m, lineCodegenModelMap); + } - if ("asyncio".equals(getLibrary())) { - additionalProperties.put("asyncio", "true"); + for (Map.Entry entry : processed.entrySet()) { + entry.setValue(postProcessModelsMap(entry.getValue())); } - modelPackage = this.packageName + "." + modelPackage; - apiPackage = this.packageName + "." + apiPackage; + return processed; } - public void setUseOneOfDiscriminatorLookup(boolean useOneOfDiscriminatorLookup) { - this.useOneOfDiscriminatorLookup = useOneOfDiscriminatorLookup; - } + private void createLineImportMapOfSet(String modelName, Map codegenModelMap) { + HashSet imports = new HashSet<>(); + lineCircularImports.put(modelName, imports); - public boolean getUseOneOfDiscriminatorLookup() { - return this.useOneOfDiscriminatorLookup; - } + CodegenModel cm = codegenModelMap.get(modelName); - @Override - public String toModelImport(String name) { - String modelImport; - if (StringUtils.startsWithAny(name, "import", "from")) { - modelImport = name; + if (cm == null) { + LOGGER.warn("Failed to lookup model in createLineImportMapOfSet: " + modelName); + return; + } + + List codegenProperties; + if (cm.oneOf != null && !cm.oneOf.isEmpty()) { + codegenProperties = cm.getComposedSchemas().getOneOf(); + } else if (cm.anyOf != null && !cm.anyOf.isEmpty()) { + codegenProperties = cm.getComposedSchemas().getAnyOf(); } else { - modelImport = "from "; - if (!"".equals(modelPackage())) { - modelImport += modelPackage() + "."; + codegenProperties = cm.vars; + } + + for (CodegenProperty cp : codegenProperties) { + String modelNameFromDataType = getLineModelNameFromDataType(cp); + if (modelNameFromDataType != null) { + imports.add(modelNameFromDataType); + updateLineImportsFromCodegenModel(modelNameFromDataType, codegenModelMap.get(modelNameFromDataType), imports); } - modelImport += toModelFilename(name) + " import " + name; } - return modelImport; } - @Override - public String getTypeDeclaration(Schema p) { - if (ModelUtils.isArraySchema(p)) { - ArraySchema ap = (ArraySchema) p; - Schema inner = ap.getItems(); - return getSchemaType(p) + "[" + getTypeDeclaration(inner) + "]"; - } else if (ModelUtils.isMapSchema(p)) { - Schema inner = ModelUtils.getAdditionalProperties(p); - - return getSchemaType(p) + "[str, " + getTypeDeclaration(inner) + "]"; + private void updateLineImportsFromCodegenModel(String modelName, CodegenModel cm, Set imports) { + if (cm == null) { + LOGGER.warn("Failed to lookup model in createLineImportMapOfSet " + modelName); + return; } - return super.getTypeDeclaration(p); - } - /* - * Gets the pydantic type given a Codegen Parameter - * - * @param cp codegen parameter - * @param typingImports typing imports - * @param pydantic pydantic imports - * @param datetimeImports datetime imports - * @param modelImports model imports - * @param exampleImports example imports - * @param classname class name - * @return pydantic type - * - */ - private String getPydanticType(CodegenParameter cp, - Set typingImports, - Set pydanticImports, - Set datetimeImports, - Set modelImports, - Set exampleImports, - String classname) { - if (cp == null) { - // if codegen parameter (e.g. map/dict of undefined type) is null, default to string - LOGGER.warn("Codegen property is null (e.g. map/dict of undefined type). Default to typing.Any."); - typingImports.add("Any"); - return "Any"; + List codegenProperties; + if (cm.oneOf != null && !cm.oneOf.isEmpty()) { + codegenProperties = cm.getComposedSchemas().getOneOf(); + } else if (cm.anyOf != null && !cm.anyOf.isEmpty()) { + codegenProperties = cm.getComposedSchemas().getAnyOf(); + } else { + codegenProperties = cm.vars; } - if (cp.isArray) { - String constraints = ""; - if (cp.maxItems != null) { - constraints += String.format(Locale.ROOT, ", max_items=%d", cp.maxItems); - } - if (cp.minItems != null) { - constraints += String.format(Locale.ROOT, ", min_items=%d", cp.minItems); - } - if (cp.getUniqueItems()) { - constraints += ", unique_items=True"; - } - pydanticImports.add("conlist"); - return String.format(Locale.ROOT, "conlist(%s%s)", - getPydanticType(cp.items, typingImports, pydanticImports, datetimeImports, modelImports, exampleImports, classname), - constraints); - } else if (cp.isMap) { - typingImports.add("Dict"); - return String.format(Locale.ROOT, "Dict[str, %s]", - getPydanticType(cp.items, typingImports, pydanticImports, datetimeImports, modelImports, exampleImports, classname)); - } else if (cp.isString) { - if (cp.hasValidation) { - List fieldCustomization = new ArrayList<>(); - // e.g. constr(regex=r'/[a-z]/i', strict=True) - fieldCustomization.add("strict=True"); - if (cp.getMaxLength() != null) { - fieldCustomization.add("max_length=" + cp.getMaxLength()); - } - if (cp.getMinLength() != null) { - fieldCustomization.add("min_length=" + cp.getMinLength()); - } - if (cp.getPattern() != null) { - pydanticImports.add("validator"); - // use validator instead as regex doesn't support flags, e.g. IGNORECASE - //fieldCustomization.add(String.format(Locale.ROOT, "regex=r'%s'", cp.getPattern())); - } - pydanticImports.add("constr"); - return String.format(Locale.ROOT, "constr(%s)", StringUtils.join(fieldCustomization, ", ")); - } else { - if ("password".equals(cp.getFormat())) { // TDOO avoid using format, use `is` boolean flag instead - pydanticImports.add("SecretStr"); - return "SecretStr"; + for (CodegenProperty cp : codegenProperties) { + String modelNameFromDataType = getLineModelNameFromDataType(cp); + if (modelNameFromDataType != null) { + if (modelName.equals(modelNameFromDataType)) { + continue; + } else if (imports.contains(modelNameFromDataType)) { + continue; } else { - pydanticImports.add("StrictStr"); - return "StrictStr"; + imports.add(modelNameFromDataType); + updateLineImportsFromCodegenModel(modelNameFromDataType, lineCodegenModelMap.get(modelNameFromDataType), imports); } } - } else if (cp.isNumber || cp.isFloat || cp.isDouble) { - if (cp.hasValidation) { - List fieldCustomization = new ArrayList<>(); - List intFieldCustomization = new ArrayList<>(); + } + } - // e.g. confloat(ge=10, le=100, strict=True) - if (cp.getMaximum() != null) { - if (cp.getExclusiveMaximum()) { - fieldCustomization.add("lt=" + cp.getMaximum()); - intFieldCustomization.add("lt=" + Math.ceil(Double.valueOf(cp.getMaximum()))); // e.g. < 7.59 becomes < 8 - } else { - fieldCustomization.add("le=" + cp.getMaximum()); - intFieldCustomization.add("le=" + Math.floor(Double.valueOf(cp.getMaximum()))); // e.g. <= 7.59 becomes <= 7 - } - } + private String getLineModelNameFromDataType(CodegenProperty cp) { + if (cp.isArray) { + return getLineModelNameFromDataType(cp.items); + } else if (cp.isMap) { + return getLineModelNameFromDataType(cp.items); + } else if (!cp.isPrimitiveType || cp.isModel) { + return cp.dataType; + } else { + return null; + } + } + + private ModelsMap postProcessModelsMap(ModelsMap objs) { + objs = postProcessModelsEnum(objs); + + TreeSet typingImports = new TreeSet<>(); + TreeSet pydanticImports = new TreeSet<>(); + TreeSet datetimeImports = new TreeSet<>(); + TreeSet modelImports = new TreeSet<>(); + + for (ModelMap m : objs.getModels()) { + TreeSet exampleImports = new TreeSet<>(); + List readOnlyFields = new ArrayList<>(); + hasModelsToImport = false; + int property_count = 1; + typingImports.clear(); + pydanticImports.clear(); + datetimeImports.clear(); + + CodegenModel model = m.getModel(); + + if (model.getComposedSchemas() != null && model.getComposedSchemas().getOneOf() != null + && !model.getComposedSchemas().getOneOf().isEmpty()) { + int index = 0; + List oneOfs = model.getComposedSchemas().getOneOf(); + for (CodegenProperty oneOf : oneOfs) { + if ("none_type".equals(oneOf.dataType)) { + oneOfs.remove(index); + break; + } + index++; + } + } + + List codegenProperties; + if (!model.oneOf.isEmpty()) { + codegenProperties = model.getComposedSchemas().getOneOf(); + typingImports.add("Any"); + typingImports.add("List"); + pydanticImports.add("Field"); + pydanticImports.add("StrictStr"); + pydanticImports.add("ValidationError"); + pydanticImports.add("validator"); + } else if (!model.anyOf.isEmpty()) { + codegenProperties = model.getComposedSchemas().getAnyOf(); + pydanticImports.add("Field"); + pydanticImports.add("StrictStr"); + pydanticImports.add("ValidationError"); + pydanticImports.add("validator"); + } else { + codegenProperties = model.vars; + if (model.getDiscriminator() != null && model.getDiscriminator().getMappedModels() != null) { + typingImports.add("Union"); + } + } + + for (CodegenProperty cp : codegenProperties) { + String typing = getPydanticType(cp, typingImports, pydanticImports, datetimeImports, modelImports, exampleImports, model.classname); + List fields = new ArrayList<>(); + String firstField = ""; + + if (cp.isReadOnly) { + readOnlyFields.add(cp.name); + } + + if (!cp.required) { + firstField = "None"; + typing = "Optional[" + typing + "]"; + typingImports.add("Optional"); + } else { + firstField = "..."; + if (cp.isNullable) { + typing = "Optional[" + typing + "]"; + typingImports.add("Optional"); + } + } + + if (cp.baseName != null && !cp.baseName.equals(cp.name)) { + fields.add(String.format(Locale.ROOT, "alias=\"%s\"", cp.baseName)); + } + + if (!StringUtils.isEmpty(cp.description)) { + fields.add(String.format(Locale.ROOT, "description=\"%s\"", cp.description)); + } + + String fieldCustomization; + if ("None".equals(firstField)) { + if (cp.defaultValue == null) { + fieldCustomization = "None"; + } else { + if (cp.isArray || cp.isMap) { + fieldCustomization = "None"; + } else { + fieldCustomization = cp.defaultValue; + } + } + } else { + fieldCustomization = firstField; + } + + if (!fields.isEmpty()) { + fields.add(0, fieldCustomization); + pydanticImports.add("Field"); + fieldCustomization = String.format(Locale.ROOT, "Field(%s)", StringUtils.join(fields, ", ")); + } + + if ("...".equals(fieldCustomization)) { + pydanticImports.add("Field"); + fieldCustomization = "Field(...)"; + } + + cp.vendorExtensions.put("x-py-typing", typing + " = " + fieldCustomization); + + if (!model.oneOf.isEmpty()) { + cp.vendorExtensions.put("x-py-name", String.format(Locale.ROOT, "oneof_schema_%d_validator", property_count++)); + } else if (!model.anyOf.isEmpty()) { + cp.vendorExtensions.put("x-py-name", String.format(Locale.ROOT, "anyof_schema_%d_validator", property_count++)); + } + } + + if (!StringUtils.isEmpty(model.parent)) { + modelImports.add(model.parent); + // Extension for discriminator + modelImports.addAll(model.imports); + + String mappedTypeName = mappingName(model.name, model.parentModel.getDiscriminator()); + model.vendorExtensions.put("x-py-type-name", mappedTypeName); + } else if (!model.isEnum) { + pydanticImports.add("BaseModel"); + } + + if (model.isEnum) { + for (Map enumVars : (List>) model.getAllowableValues().get("enumVars")) { + if ((Boolean) enumVars.get("isString")) { + model.vendorExtensions.put("x-py-enum-type", "str"); + enumVars.put("name", toEnumVariableName((String) enumVars.get("value"), "str")); + } else { + model.vendorExtensions.put("x-py-enum-type", "int"); + enumVars.put("name", toEnumVariableName((String) enumVars.get("value"), "int")); + } + } + } + + model.getVendorExtensions().put("x-py-typing-imports", typingImports); + model.getVendorExtensions().put("x-py-pydantic-imports", pydanticImports); + model.getVendorExtensions().put("x-py-datetime-imports", datetimeImports); + model.getVendorExtensions().put("x-py-readonly", readOnlyFields); + + if (!modelImports.isEmpty()) { + Set modelsToImport = new TreeSet<>(); + for (String modelImport : modelImports) { + if (modelImport.equals(model.classname)) { + continue; + } + modelsToImport.add("from " + packageName + ".models." + underscore(modelImport) + " import " + modelImport); + } + + model.getVendorExtensions().put("x-py-model-imports", modelsToImport); + } + } + + return objs; + } + + private String mappingName(String modelName, CodegenDiscriminator discriminator) { + String valueToSearch = "#/components/schemas/" + modelName; + return discriminator.getMapping().entrySet().stream() + .filter(entry -> valueToSearch.equals(entry.getValue())) + .map(Map.Entry::getKey) + .findFirst() + .orElseThrow(() -> new NoSuchElementException("Key not found (" + modelName + ") mapping (" + discriminator.getMapping() + ")")); + } + + // Pydantic type resolver (CodegenParameter). The upstream version adds + // postponedModelImports / postponedExampleImports arguments and emits Annotated + // imports; we keep the original signature to stay on pydantic.v1 without + // postponed imports. + private String getPydanticType(CodegenParameter cp, + Set typingImports, + Set pydanticImports, + Set datetimeImports, + Set modelImports, + Set exampleImports, + String classname) { + if (cp == null) { + LOGGER.warn("Codegen property is null (e.g. map/dict of undefined type). Default to typing.Any."); + typingImports.add("Any"); + return "Any"; + } + + if (cp.isArray) { + String constraints = ""; + if (cp.maxItems != null) { + constraints += String.format(Locale.ROOT, ", max_items=%d", cp.maxItems); + } + if (cp.minItems != null) { + constraints += String.format(Locale.ROOT, ", min_items=%d", cp.minItems); + } + if (cp.getUniqueItems()) { + constraints += ", unique_items=True"; + } + pydanticImports.add("conlist"); + return String.format(Locale.ROOT, "conlist(%s%s)", + getPydanticType(cp.items, typingImports, pydanticImports, datetimeImports, modelImports, exampleImports, classname), + constraints); + } else if (cp.isMap) { + typingImports.add("Dict"); + return String.format(Locale.ROOT, "Dict[str, %s]", + getPydanticType(cp.items, typingImports, pydanticImports, datetimeImports, modelImports, exampleImports, classname)); + } else if (cp.isString) { + if (cp.hasValidation) { + List fieldCustomization = new ArrayList<>(); + fieldCustomization.add("strict=True"); + if (cp.getMaxLength() != null) { + fieldCustomization.add("max_length=" + cp.getMaxLength()); + } + if (cp.getMinLength() != null) { + fieldCustomization.add("min_length=" + cp.getMinLength()); + } + if (cp.getPattern() != null) { + pydanticImports.add("validator"); + } + pydanticImports.add("constr"); + return String.format(Locale.ROOT, "constr(%s)", StringUtils.join(fieldCustomization, ", ")); + } else { + if ("password".equals(cp.getFormat())) { + pydanticImports.add("SecretStr"); + return "SecretStr"; + } else { + pydanticImports.add("StrictStr"); + return "StrictStr"; + } + } + } else if (cp.isNumber || cp.isFloat || cp.isDouble) { + if (cp.hasValidation) { + List fieldCustomization = new ArrayList<>(); + List intFieldCustomization = new ArrayList<>(); + + if (cp.getMaximum() != null) { + if (cp.getExclusiveMaximum()) { + fieldCustomization.add("lt=" + cp.getMaximum()); + intFieldCustomization.add("lt=" + Math.ceil(Double.valueOf(cp.getMaximum()))); + } else { + fieldCustomization.add("le=" + cp.getMaximum()); + intFieldCustomization.add("le=" + Math.floor(Double.valueOf(cp.getMaximum()))); + } + } if (cp.getMinimum() != null) { if (cp.getExclusiveMinimum()) { fieldCustomization.add("gt=" + cp.getMinimum()); - intFieldCustomization.add("gt=" + Math.floor(Double.valueOf(cp.getMinimum()))); // e.g. > 7.59 becomes > 7 + intFieldCustomization.add("gt=" + Math.floor(Double.valueOf(cp.getMinimum()))); } else { fieldCustomization.add("ge=" + cp.getMinimum()); - intFieldCustomization.add("ge=" + Math.ceil(Double.valueOf(cp.getMinimum()))); // e.g. >= 7.59 becomes >= 8 + intFieldCustomization.add("ge=" + Math.ceil(Double.valueOf(cp.getMinimum()))); } } if (cp.getMultipleOf() != null) { @@ -530,7 +643,7 @@ private String getPydanticType(CodegenParameter cp, pydanticImports.add("confloat"); return String.format(Locale.ROOT, "%s(%s)", "confloat", StringUtils.join(fieldCustomization, ", ")); - } else { // float + } else { pydanticImports.add("confloat"); return String.format(Locale.ROOT, "%s(%s)", "confloat", StringUtils.join(fieldCustomization, ", ")); @@ -551,7 +664,6 @@ private String getPydanticType(CodegenParameter cp, } else if (cp.isInteger || cp.isLong || cp.isShort || cp.isUnboundedInteger) { if (cp.hasValidation) { List fieldCustomization = new ArrayList<>(); - // e.g. conint(ge=10, le=100, strict=True) fieldCustomization.add("strict=True"); if (cp.getMaximum() != null) { if (cp.getExclusiveMaximum()) { @@ -581,7 +693,6 @@ private String getPydanticType(CodegenParameter cp, } else if (cp.isBinary || cp.isByteArray) { if (cp.hasValidation) { List fieldCustomization = new ArrayList<>(); - // e.g. conbytes(min_length=2, max_length=10) fieldCustomization.add("strict=True"); if (cp.getMinLength() != null) { fieldCustomization.add("min_length=" + cp.getMinLength()); @@ -591,8 +702,6 @@ private String getPydanticType(CodegenParameter cp, } if (cp.getPattern() != null) { pydanticImports.add("validator"); - // use validator instead as regex doesn't support flags, e.g. IGNORECASE - //fieldCustomization.add(Locale.ROOT, String.format(Locale.ROOT, "regex=r'%s'", cp.getPattern())); } pydanticImports.add("conbytes"); @@ -600,7 +709,6 @@ private String getPydanticType(CodegenParameter cp, typingImports.add("Union"); return String.format(Locale.ROOT, "Union[conbytes(%s), constr(% fieldCustomization = new ArrayList<>(); - // e.g. condecimal(ge=10, le=100, strict=True) fieldCustomization.add("strict=True"); if (cp.getMaximum() != null) { if (cp.getExclusiveMaximum()) { @@ -647,16 +754,14 @@ private String getPydanticType(CodegenParameter cp, if (cp.isDateTime) { datetimeImports.add("datetime"); } - return cp.dataType; } else if (cp.isUuid) { return cp.dataType; - } else if (cp.isFreeFormObject) { // type: object + } else if (cp.isFreeFormObject) { typingImports.add("Dict"); typingImports.add("Any"); return "Dict[str, Any]"; } else if (!cp.isPrimitiveType) { - // add model prefix hasModelsToImport = true; modelImports.add(cp.dataType); exampleImports.add(cp.dataType); @@ -665,7 +770,6 @@ private String getPydanticType(CodegenParameter cp, LinkedHashMap contents = cp.getContent(); for (String key : contents.keySet()) { CodegenMediaType cmt = contents.get(key); - // TODO process the first one only at the moment if (cmt != null) return getPydanticType(cmt.getSchema(), typingImports, pydanticImports, datetimeImports, modelImports, exampleImports, classname); } @@ -675,19 +779,7 @@ private String getPydanticType(CodegenParameter cp, } } - /* - * Gets the pydantic type given a Codegen Property - * - * @param cp codegen property - * @param typingImports typing imports - * @param pydantic pydantic imports - * @param datetimeImports datetime imports - * @param modelImports model imports - * @param exampleImports example imports - * @param classname class name - * @return pydantic type - * - */ + // Pydantic type resolver (CodegenProperty). private String getPydanticType(CodegenProperty cp, Set typingImports, Set pydanticImports, @@ -696,7 +788,6 @@ private String getPydanticType(CodegenProperty cp, Set exampleImports, String classname) { if (cp == null) { - // if codegen property (e.g. map/dict of undefined type) is null, default to string LOGGER.warn("Codegen property is null (e.g. map/dict of undefined type). Default to typing.Any."); typingImports.add("Any"); return "Any"; @@ -706,20 +797,6 @@ private String getPydanticType(CodegenProperty cp, pydanticImports.add("validator"); } - /* comment out the following since Literal requires python 3.8 - also need to put cp.isEnum check after isArray, isMap check - if (cp.isEnum) { - // use Literal for inline enum - typingImports.add("Literal"); - List values = new ArrayList<>(); - List> enumVars = (List>) cp.allowableValues.get("enumVars"); - if (enumVars != null) { - for (Map enumVar : enumVars) { - values.add((String) enumVar.get("value")); - } - } - return String.format(Locale.ROOT, "%sEnum", cp.nameInCamelCase); - } else*/ if (cp.isArray) { String constraints = ""; if (cp.maxItems != null) { @@ -732,7 +809,7 @@ private String getPydanticType(CodegenProperty cp, constraints += ", unique_items=True"; } pydanticImports.add("conlist"); - typingImports.add("List"); // for return type + typingImports.add("List"); return String.format(Locale.ROOT, "conlist(%s%s)", getPydanticType(cp.items, typingImports, pydanticImports, datetimeImports, modelImports, exampleImports, classname), constraints); @@ -742,7 +819,6 @@ private String getPydanticType(CodegenProperty cp, } else if (cp.isString) { if (cp.hasValidation) { List fieldCustomization = new ArrayList<>(); - // e.g. constr(regex=r'/[a-z]/i', strict=True) fieldCustomization.add("strict=True"); if (cp.getMaxLength() != null) { fieldCustomization.add("max_length=" + cp.getMaxLength()); @@ -752,13 +828,11 @@ private String getPydanticType(CodegenProperty cp, } if (cp.getPattern() != null) { pydanticImports.add("validator"); - // use validator instead as regex doesn't support flags, e.g. IGNORECASE - //fieldCustomization.add(Locale.ROOT, String.format(Locale.ROOT, "regex=r'%s'", cp.getPattern())); } pydanticImports.add("constr"); return String.format(Locale.ROOT, "constr(%s)", StringUtils.join(fieldCustomization, ", ")); } else { - if ("password".equals(cp.getFormat())) { // TDOO avoid using format, use `is` boolean flag instead + if ("password".equals(cp.getFormat())) { pydanticImports.add("SecretStr"); return "SecretStr"; } else { @@ -771,23 +845,22 @@ private String getPydanticType(CodegenProperty cp, List fieldCustomization = new ArrayList<>(); List intFieldCustomization = new ArrayList<>(); - // e.g. confloat(ge=10, le=100, strict=True) if (cp.getMaximum() != null) { if (cp.getExclusiveMaximum()) { fieldCustomization.add("lt=" + cp.getMaximum()); - intFieldCustomization.add("lt=" + (int) Math.ceil(Double.valueOf(cp.getMaximum()))); // e.g. < 7.59 => < 8 + intFieldCustomization.add("lt=" + (int) Math.ceil(Double.valueOf(cp.getMaximum()))); } else { fieldCustomization.add("le=" + cp.getMaximum()); - intFieldCustomization.add("le=" + (int) Math.floor(Double.valueOf(cp.getMaximum()))); // e.g. <= 7.59 => <= 7 + intFieldCustomization.add("le=" + (int) Math.floor(Double.valueOf(cp.getMaximum()))); } } if (cp.getMinimum() != null) { if (cp.getExclusiveMinimum()) { fieldCustomization.add("gt=" + cp.getMinimum()); - intFieldCustomization.add("gt=" + (int) Math.floor(Double.valueOf(cp.getMinimum()))); // e.g. > 7.59 => > 7 + intFieldCustomization.add("gt=" + (int) Math.floor(Double.valueOf(cp.getMinimum()))); } else { fieldCustomization.add("ge=" + cp.getMinimum()); - intFieldCustomization.add("ge=" + (int) Math.ceil(Double.valueOf(cp.getMinimum()))); // e.g. >= 7.59 => >= 8 + intFieldCustomization.add("ge=" + (int) Math.ceil(Double.valueOf(cp.getMinimum()))); } } if (cp.getMultipleOf() != null) { @@ -810,7 +883,7 @@ private String getPydanticType(CodegenProperty cp, pydanticImports.add("confloat"); return String.format(Locale.ROOT, "%s(%s)", "confloat", StringUtils.join(fieldCustomization, ", ")); - } else { // float + } else { pydanticImports.add("confloat"); return String.format(Locale.ROOT, "%s(%s)", "confloat", StringUtils.join(fieldCustomization, ", ")); @@ -831,7 +904,6 @@ private String getPydanticType(CodegenProperty cp, } else if (cp.isInteger || cp.isLong || cp.isShort || cp.isUnboundedInteger) { if (cp.hasValidation) { List fieldCustomization = new ArrayList<>(); - // e.g. conint(ge=10, le=100, strict=True) fieldCustomization.add("strict=True"); if (cp.getMaximum() != null) { if (cp.getExclusiveMaximum()) { @@ -861,7 +933,6 @@ private String getPydanticType(CodegenProperty cp, } else if (cp.isBinary || cp.isByteArray) { if (cp.hasValidation) { List fieldCustomization = new ArrayList<>(); - // e.g. conbytes(min_length=2, max_length=10) fieldCustomization.add("strict=True"); if (cp.getMinLength() != null) { fieldCustomization.add("min_length=" + cp.getMinLength()); @@ -871,8 +942,6 @@ private String getPydanticType(CodegenProperty cp, } if (cp.getPattern() != null) { pydanticImports.add("validator"); - // use validator instead as regex doesn't support flags, e.g. IGNORECASE - //fieldCustomization.add(Locale.ROOT, String.format(Locale.ROOT, "regex=r'%s'", cp.getPattern())); } pydanticImports.add("conbytes"); @@ -880,7 +949,6 @@ private String getPydanticType(CodegenProperty cp, typingImports.add("Union"); return String.format(Locale.ROOT, "Union[conbytes(%s), constr(% fieldCustomization = new ArrayList<>(); - // e.g. condecimal(ge=10, le=100, strict=True) fieldCustomization.add("strict=True"); if (cp.getMaximum() != null) { if (cp.getExclusiveMaximum()) { @@ -930,24 +997,20 @@ private String getPydanticType(CodegenProperty cp, return cp.dataType; } else if (cp.isUuid) { return cp.dataType; - } else if (cp.isFreeFormObject) { // type: object + } else if (cp.isFreeFormObject) { typingImports.add("Dict"); typingImports.add("Any"); return "Dict[str, Any]"; - } else if (!cp.isPrimitiveType || cp.isModel) { // model - // skip import if it's a circular reference + } else if (!cp.isPrimitiveType || cp.isModel) { if (classname == null) { - // for parameter model, import directly hasModelsToImport = true; modelImports.add(cp.dataType); exampleImports.add(cp.dataType); } else { - if (circularImports.containsKey(cp.dataType)) { - if (circularImports.get(cp.dataType).contains(classname)) { - // cp.dataType import map of set contains this model (classname), don't import + if (lineCircularImports.containsKey(cp.dataType)) { + if (lineCircularImports.get(cp.dataType).contains(classname)) { LOGGER.debug("Skipped importing {} in {} due to circular import.", cp.dataType, classname); } else { - // not circular import, so ok to import it hasModelsToImport = true; modelImports.add(cp.dataType); exampleImports.add(cp.dataType); @@ -961,706 +1024,4 @@ private String getPydanticType(CodegenProperty cp, throw new RuntimeException("Error! Codegen Property not yet supported in getPydanticType: " + cp); } } - - @Override - public OperationsMap postProcessOperationsWithModels(OperationsMap objs, List allModels) { - hasModelsToImport = false; - TreeSet typingImports = new TreeSet<>(); - TreeSet pydanticImports = new TreeSet<>(); - TreeSet datetimeImports = new TreeSet<>(); - TreeSet modelImports = new TreeSet<>(); - - OperationMap objectMap = objs.getOperations(); - List operations = objectMap.getOperation(); - for (CodegenOperation operation : operations) { - TreeSet exampleImports = new TreeSet<>(); // import for each operation to be show in sample code - List params = operation.allParams; - - for (CodegenParameter param : params) { - String typing = getPydanticType(param, typingImports, pydanticImports, datetimeImports, modelImports, exampleImports, null); - List fields = new ArrayList<>(); - String firstField = ""; - - if (!param.required) { //optional - firstField = "None"; - typing = "Optional[" + typing + "]"; - typingImports.add("Optional"); - } else { // required - firstField = "..."; - if (param.isNullable) { - typing = "Optional[" + typing + "]"; - typingImports.add("Optional"); - } - } - - if (!StringUtils.isEmpty(param.description)) { // has description - fields.add(String.format(Locale.ROOT, "description=\"%s\"", param.description)); - } - - /* TODO support example - if (!StringUtils.isEmpty(cp.getExample())) { // has example - fields.add(String.format(Locale.ROOT, "example=%s", cp.getExample())); - }*/ - - String fieldCustomization; - if ("None".equals(firstField)) { - fieldCustomization = null; - } else { // required field - fieldCustomization = firstField; - } - - if (!fields.isEmpty()) { - if (fieldCustomization != null) { - fields.add(0, fieldCustomization); - } - pydanticImports.add("Field"); - fieldCustomization = String.format(Locale.ROOT, "Field(%s)", StringUtils.join(fields, ", ")); - } else { - fieldCustomization = "Field()"; - } - - if ("Field()".equals(fieldCustomization)) { - param.vendorExtensions.put("x-py-typing", typing); - } else { - param.vendorExtensions.put("x-py-typing", String.format(Locale.ROOT, "Annotated[%s, %s]", typing, fieldCustomization)); - } - } - - // update typing import for operation return type - if (!StringUtils.isEmpty(operation.returnType)) { - String typing = getPydanticType(operation.returnProperty, typingImports, - new TreeSet<>() /* skip pydantic import for return type */, datetimeImports, modelImports, exampleImports, null); - } - - // add import for code samples - // import models one by one - if (!exampleImports.isEmpty()) { - List imports = new ArrayList<>(); - for (String exampleImport : exampleImports) { - imports.add("from " + packageName + ".models." + underscore(exampleImport) + " import " + exampleImport); - } - operation.vendorExtensions.put("x-py-example-import", imports); - } - } - - List> newImports = new ArrayList<>(); - - // need datetime import - if (!datetimeImports.isEmpty()) { - Map item = new HashMap<>(); - item.put("import", String.format(Locale.ROOT, "from datetime import %s\n", StringUtils.join(datetimeImports, ", "))); - newImports.add(item); - } - - // need pydantic imports - if (!pydanticImports.isEmpty()) { - Map item = new HashMap<>(); - item.put("import", String.format(Locale.ROOT, "from pydantic.v1 import %s\n", StringUtils.join(pydanticImports, ", "))); - newImports.add(item); - } - - // need typing imports - if (!typingImports.isEmpty()) { - Map item = new HashMap<>(); - item.put("import", String.format(Locale.ROOT, "from typing import %s\n", StringUtils.join(typingImports, ", "))); - newImports.add(item); - } - - // import models one by one - if (!modelImports.isEmpty()) { - for (String modelImport : modelImports) { - Map item = new HashMap<>(); - item.put("import", "from " + packageName + ".models." + underscore(modelImport) + " import " + modelImport); - newImports.add(item); - } - } - - // reset imports with newImports - objs.setImports(newImports); - return objs; - } - - @Override - public Map postProcessAllModels(Map objs) { - final Map processed = super.postProcessAllModels(objs); - - for (Map.Entry entry : objs.entrySet()) { - // create hash map of codegen model - CodegenModel cm = ModelUtils.getModelByName(entry.getKey(), objs); - codegenModelMap.put(cm.classname, ModelUtils.getModelByName(entry.getKey(), objs)); - } - - // create circular import - for (String m : codegenModelMap.keySet()) { - createImportMapOfSet(m, codegenModelMap); - } - - for (Map.Entry entry : processed.entrySet()) { - entry.setValue(postProcessModelsMap(entry.getValue())); - } - - return processed; - } - - /** - * Update circularImports with the model name (key) and its imports gathered recursively - * - * @param modelName model name - * @param codegenModelMap a map of CodegenModel - */ - void createImportMapOfSet(String modelName, Map codegenModelMap) { - HashSet imports = new HashSet<>(); - circularImports.put(modelName, imports); - - CodegenModel cm = codegenModelMap.get(modelName); - - if (cm == null) { - LOGGER.warn("Failed to lookup model in createImportMapOfSet: " + modelName); - return; - } - - List codegenProperties = null; - if (cm.oneOf != null && !cm.oneOf.isEmpty()) { // oneOf - codegenProperties = cm.getComposedSchemas().getOneOf(); - } else if (cm.anyOf != null && !cm.anyOf.isEmpty()) { // anyOF - codegenProperties = cm.getComposedSchemas().getAnyOf(); - } else { // typical model - codegenProperties = cm.vars; - } - - for (CodegenProperty cp : codegenProperties) { - String modelNameFromDataType = getModelNameFromDataType(cp); - if (modelNameFromDataType != null) { // model - imports.add(modelNameFromDataType); // update import - // go through properties or sub-schemas of the model recursively to identify more (model) import if any - updateImportsFromCodegenModel(modelNameFromDataType, codegenModelMap.get(modelNameFromDataType), imports); - } - } - } - - /** - * Update set of imports from codegen model recursivly - * - * @param modelName model name - * @param cm codegen model - * @param imports set of imports - */ - public void updateImportsFromCodegenModel(String modelName, CodegenModel cm, Set imports) { - if (cm == null) { - LOGGER.warn("Failed to lookup model in createImportMapOfSet " + modelName); - return; - } - - List codegenProperties = null; - if (cm.oneOf != null && !cm.oneOf.isEmpty()) { // oneOfValidationError - codegenProperties = cm.getComposedSchemas().getOneOf(); - } else if (cm.anyOf != null && !cm.anyOf.isEmpty()) { // anyOF - codegenProperties = cm.getComposedSchemas().getAnyOf(); - } else { // typical model - codegenProperties = cm.vars; - } - - for (CodegenProperty cp : codegenProperties) { - String modelNameFromDataType = getModelNameFromDataType(cp); - if (modelNameFromDataType != null) { // model - if (modelName.equals(modelNameFromDataType)) { // self referencing - continue; - } else if (imports.contains(modelNameFromDataType)) { // circular import - continue; - } else { - imports.add(modelNameFromDataType); // update import - // go through properties of the model recursively to identify more (model) import if any - updateImportsFromCodegenModel(modelNameFromDataType, codegenModelMap.get(modelNameFromDataType), imports); - } - } - } - } - - /** - * Returns the model name (if any) from data type of codegen property. - * Returns null if it's not a model. - * - * @param cp Codegen property - * @return model name - */ - private String getModelNameFromDataType(CodegenProperty cp) { - if (cp.isArray) { - return getModelNameFromDataType(cp.items); - } else if (cp.isMap) { - return getModelNameFromDataType(cp.items); - } else if (!cp.isPrimitiveType || cp.isModel) { - return cp.dataType; - } else { - return null; - } - } - - private ModelsMap postProcessModelsMap(ModelsMap objs) { - // process enum in models - objs = postProcessModelsEnum(objs); - - TreeSet typingImports = new TreeSet<>(); - TreeSet pydanticImports = new TreeSet<>(); - TreeSet datetimeImports = new TreeSet<>(); - TreeSet modelImports = new TreeSet<>(); - - for (ModelMap m : objs.getModels()) { - TreeSet exampleImports = new TreeSet<>(); - List readOnlyFields = new ArrayList<>(); - hasModelsToImport = false; - int property_count = 1; - typingImports.clear(); - pydanticImports.clear(); - datetimeImports.clear(); - - CodegenModel model = m.getModel(); - - // handle null type in oneOf - if (model.getComposedSchemas() != null && model.getComposedSchemas().getOneOf() != null - && !model.getComposedSchemas().getOneOf().isEmpty()) { - int index = 0; - List oneOfs = model.getComposedSchemas().getOneOf(); - for (CodegenProperty oneOf : oneOfs) { - if ("none_type".equals(oneOf.dataType)) { - oneOfs.remove(index); - break; // return earlier assuming there's only 1 null type defined - } - index++; - } - } - - List codegenProperties = null; - if (!model.oneOf.isEmpty()) { // oneOfValidationError - codegenProperties = model.getComposedSchemas().getOneOf(); - typingImports.add("Any"); - typingImports.add("List"); - pydanticImports.add("Field"); - pydanticImports.add("StrictStr"); - pydanticImports.add("ValidationError"); - pydanticImports.add("validator"); - } else if (!model.anyOf.isEmpty()) { // anyOF - codegenProperties = model.getComposedSchemas().getAnyOf(); - pydanticImports.add("Field"); - pydanticImports.add("StrictStr"); - pydanticImports.add("ValidationError"); - pydanticImports.add("validator"); - } else { // typical model - codegenProperties = model.vars; - if (model.getDiscriminator() != null && model.getDiscriminator().getMappedModels() != null) { - typingImports.add("Union"); - } - } - - //loop through properties/schemas to set up typing, pydantic - for (CodegenProperty cp : codegenProperties) { - String typing = getPydanticType(cp, typingImports, pydanticImports, datetimeImports, modelImports, exampleImports, model.classname); - List fields = new ArrayList<>(); - String firstField = ""; - - // is readOnly? - if (cp.isReadOnly) { - readOnlyFields.add(cp.name); - } - - if (!cp.required) { //optional - firstField = "None"; - typing = "Optional[" + typing + "]"; - typingImports.add("Optional"); - } else { // required - firstField = "..."; - if (cp.isNullable) { - typing = "Optional[" + typing + "]"; - typingImports.add("Optional"); - } - } - - // field - if (cp.baseName != null && !cp.baseName.equals(cp.name)) { // base name not the same as name - fields.add(String.format(Locale.ROOT, "alias=\"%s\"", cp.baseName)); - } - - if (!StringUtils.isEmpty(cp.description)) { // has description - fields.add(String.format(Locale.ROOT, "description=\"%s\"", cp.description)); - } - - /* TODO review as example may break the build - if (!StringUtils.isEmpty(cp.getExample())) { // has example - fields.add(String.format(Locale.ROOT, "example=%s", cp.getExample())); - }*/ - - String fieldCustomization; - if ("None".equals(firstField)) { - if (cp.defaultValue == null) { - fieldCustomization = "None"; - } else { - if (cp.isArray || cp.isMap) { - // TODO handle default value for array/map - fieldCustomization = "None"; - } else { - fieldCustomization = cp.defaultValue; - } - } - } else { // required field - fieldCustomization = firstField; - } - - if (!fields.isEmpty()) { - fields.add(0, fieldCustomization); - pydanticImports.add("Field"); - fieldCustomization = String.format(Locale.ROOT, "Field(%s)", StringUtils.join(fields, ", ")); - } - - if ("...".equals(fieldCustomization)) { - // use Field() to avoid pylint warnings - pydanticImports.add("Field"); - fieldCustomization = "Field(...)"; - } - - cp.vendorExtensions.put("x-py-typing", typing + " = " + fieldCustomization); - - // setup x-py-name for each oneOf/anyOf schema - if (!model.oneOf.isEmpty()) { // oneOf - cp.vendorExtensions.put("x-py-name", String.format(Locale.ROOT, "oneof_schema_%d_validator", property_count++)); - } else if (!model.anyOf.isEmpty()) { // anyOf - cp.vendorExtensions.put("x-py-name", String.format(Locale.ROOT, "anyof_schema_%d_validator", property_count++)); - } - } - - // add parent model to import - if (!StringUtils.isEmpty(model.parent)) { - modelImports.add(model.parent); - // Extention for discriminator - modelImports.addAll(model.imports); - - String mappedTypeName = mappingName(model.name, model.parentModel.getDiscriminator()); - model.vendorExtensions.put("x-py-type-name", mappedTypeName); - } else if (!model.isEnum) { - pydanticImports.add("BaseModel"); - } - - // set enum type in extensions and update `name` in enumVars - if (model.isEnum) { - for (Map enumVars : (List>) model.getAllowableValues().get("enumVars")) { - if ((Boolean) enumVars.get("isString")) { - model.vendorExtensions.put("x-py-enum-type", "str"); - // update `name`, e.g. - enumVars.put("name", toEnumVariableName((String) enumVars.get("value"), "str")); - } else { - model.vendorExtensions.put("x-py-enum-type", "int"); - enumVars.put("name", toEnumVariableName((String) enumVars.get("value"), "int")); - } - } - } - - // set the extensions if the key is absent - model.getVendorExtensions().put("x-py-typing-imports", typingImports); - model.getVendorExtensions().put("x-py-pydantic-imports", pydanticImports); - model.getVendorExtensions().put("x-py-datetime-imports", datetimeImports); - model.getVendorExtensions().put("x-py-readonly", readOnlyFields); - - // import models one by one - if (!modelImports.isEmpty()) { - Set modelsToImport = new TreeSet<>(); - for (String modelImport : modelImports) { - if (modelImport.equals(model.classname)) { - // skip self import - continue; - } - modelsToImport.add("from " + packageName + ".models." + underscore(modelImport) + " import " + modelImport); - } - - model.getVendorExtensions().put("x-py-model-imports", modelsToImport); - } - } - - return objs; - } - - private String mappingName(String modelName, CodegenDiscriminator discriminator) { - String valueToSearch = "#/components/schemas/" + modelName; - return discriminator.getMapping().entrySet().stream() - .filter(entry -> valueToSearch.equals(entry.getValue())) - .map(Map.Entry::getKey) - .findFirst() - .orElseThrow(() -> new NoSuchElementException("Key not found (" + modelName + ") mapping (" + discriminator.getMapping() + ")")); - } - - @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 OpenAPI pattern spec follows the Perl convention and style of modifiers. Python - * does not support this in as natural a way so it needs to convert it. See - * https://docs.python.org/2/howto/regex.html#compilation-flags for details. - * - * @param pattern (the String pattern to convert from python to Perl convention) - * @param vendorExtensions (list of custom x-* properties for extra functionality-see https://swagger.io/docs/specification/openapi-extensions/) - * @return void - * @throws IllegalArgumentException if pattern does not follow the Perl /pattern/modifiers convention - * - * Includes fix for issue #6675 - */ - public void postProcessPattern(String pattern, Map vendorExtensions) { - if (pattern != null) { - int i = pattern.lastIndexOf('/'); - - // TOOD update the check below follow python convention - //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.replace("\"", "\\\"")); - vendorExtensions.put("x-pattern", pattern.replace("\"", "\\\"")); - vendorExtensions.put("x-modifiers", modifiers); - } - } - - @Override - public CodegenType getTag() { - return CodegenType.CLIENT; - } - - @Override - public String getName() { - return "python-nextgen-custom-client"; - } - - @Override - public String getHelp() { - return "Generates a Python client library."; - } - - - @Override - public String apiDocFileFolder() { - return (outputFolder + File.separator + apiDocPath); - } - - @Override - public String modelDocFileFolder() { - return (outputFolder + File.separator + modelDocPath); - } - - @Override - public String toModelDocFilename(String name) { - return toModelName(name); - } - - @Override - public String toApiDocFilename(String name) { - return toApiName(name); - } - - @Override - public String toApiName(String name) { - return camelize(name); - } - - @Override - public String addRegularExpressionDelimiter(String pattern) { - if (StringUtils.isEmpty(pattern)) { - return pattern; - } - - if (!pattern.matches("^/.*")) { - // Perform a negative lookbehind on each `/` to ensure that it is escaped. - return "/" + pattern.replaceAll("(? - * (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]+", "")); - } - - @Override - public String generatorLanguageVersion() { - return "3.10+"; - } - - @Override - protected void addAdditionPropertiesToCodeGenModel(CodegenModel codegenModel, Schema schema) { - final Schema additionalProperties = ModelUtils.getAdditionalProperties(schema); - - if (additionalProperties != null) { - codegenModel.additionalPropertiesType = getSchemaType(additionalProperties); - } - } - - @Override - public String toEnumVarName(String name, String datatype) { - if ("int".equals(datatype) || "float".equals(datatype)) { - return name; - } else { - return "\'" + name + "\'"; - } - } - - public String toEnumVariableName(String name, String datatype) { - if ("int".equals(datatype)) { - return "NUMBER_" + name.replace("-", "MINUS_"); - } - - // remove quote e.g. 'abc' => abc - name = name.substring(1, name.length() - 1); - - if (name.length() == 0) { - return "EMPTY"; - } - - if (" ".equals(name)) { - return "SPACE"; - } - - if ("_".equals(name)) { - return "UNDERSCORE"; - } - - if (reservedWords.contains(name)) { - name = name.toUpperCase(Locale.ROOT); - } else if (((CharSequence) name).chars().anyMatch(character -> specialCharReplacements.keySet().contains(String.valueOf((char) character)))) { - name = underscore(escape(name, specialCharReplacements, Collections.singletonList("_"), "_")).toUpperCase(Locale.ROOT); - } else { - name = name.toUpperCase(Locale.ROOT); - } - - name = name.replace(" ", "_"); - name = name.replaceFirst("^_", ""); - name = name.replaceFirst("_$", ""); - - if (name.matches("\\d.*")) { - name = "ENUM_" + name.toUpperCase(Locale.ROOT); - } - - return name; - } - - @Override - public String toEnumValue(String value, String datatype) { - if ("int".equals(datatype) || "float".equals(datatype)) { - return value; - } else { - return "\'" + escapeText(value) + "\'"; - } - } - - @Override - public String toEnumDefaultValue(String value, String datatype) { - return value; - } - - /** - * checks if the data should be classified as "string" in enum - * e.g. double in C# needs to be double-quoted (e.g. "2.8") by treating it as a string - * In the future, we may rename this function to "isEnumString" - * - * @param dataType data type - * @return true if it's a enum string - */ - @Override - public boolean isDataTypeString(String dataType) { - return "str".equals(dataType); - } - - @Override - public String escapeReservedWord(String name) { - if (this.reservedWordsMappings().containsKey(name)) { - return this.reservedWordsMappings().get(name); - } - return "var_" + name; - } - - public void setMapNumberTo(String mapNumberTo) { - if ("Union[StrictFloat, StrictInt]".equals(mapNumberTo) - || "StrictFloat".equals(mapNumberTo) - || "float".equals(mapNumberTo)) { - this.mapNumberTo = mapNumberTo; - } else { - throw new IllegalArgumentException("mapNumberTo value must be Union[StrictFloat, StrictInt], StrictStr or float"); - } - } - - public void setDatetimeFormat(String datetimeFormat) { - this.datetimeFormat = datetimeFormat; - } - - public void setDateFormat(String dateFormat) { - this.dateFormat = dateFormat; - } - - @Override - public ModelsMap postProcessModels(ModelsMap objs) { - // process enum in models - return postProcessModelsEnum(objs); - } } diff --git a/generator/src/main/java/line/bot/generator/pebble/PebbleTemplateAdapter.java b/generator/src/main/java/line/bot/generator/pebble/PebbleTemplateAdapter.java new file mode 100644 index 000000000..88e6781b9 --- /dev/null +++ b/generator/src/main/java/line/bot/generator/pebble/PebbleTemplateAdapter.java @@ -0,0 +1,53 @@ +package line.bot.generator.pebble; + +import io.pebbletemplates.pebble.PebbleEngine; +import io.pebbletemplates.pebble.loader.ClasspathLoader; +import org.openapitools.codegen.api.AbstractTemplatingEngineAdapter; +import org.openapitools.codegen.api.TemplatingExecutor; + +import java.io.IOException; +import java.io.StringWriter; +import java.util.Map; + +public class PebbleTemplateAdapter extends AbstractTemplatingEngineAdapter { + private static final String[] EXTENSIONS = new String[]{"pebble"}; + + private final PebbleEngine engine = new PebbleEngine.Builder() + .cacheActive(false) + .newLineTrimming(false) + .loader(buildLoader()) + .autoEscaping(false) + .build(); + + private static ClasspathLoader buildLoader() { + // OpenAPI Generator passes the template file relative to templateDir + // (e.g. "model.pebble"), so the loader resolves it under our template dir. + ClasspathLoader loader = new ClasspathLoader(); + loader.setPrefix("python-nextgen-custom-client/"); + loader.setSuffix(""); + return loader; + } + + public PebbleTemplateAdapter() { + super(); + } + + @Override + public String getIdentifier() { + return "pebble"; + } + + @Override + public String[] getFileExtensions() { + return EXTENSIONS; + } + + @Override + public String compileTemplate(TemplatingExecutor generator, Map bundle, String templateFile) throws IOException { + String modifiedTemplate = this.getModifiedFileLocation(templateFile)[0]; + + StringWriter writer = new StringWriter(); + engine.getTemplate(modifiedTemplate).evaluate(writer, bundle); + return writer.toString(); + } +} diff --git a/generator/src/main/resources/META-INF/services/org.openapitools.codegen.api.TemplatingEngineAdapter b/generator/src/main/resources/META-INF/services/org.openapitools.codegen.api.TemplatingEngineAdapter new file mode 100644 index 000000000..bc773bc91 --- /dev/null +++ b/generator/src/main/resources/META-INF/services/org.openapitools.codegen.api.TemplatingEngineAdapter @@ -0,0 +1 @@ +line.bot.generator.pebble.PebbleTemplateAdapter diff --git a/generator/src/main/resources/python-nextgen-custom-client/README.mustache b/generator/src/main/resources/python-nextgen-custom-client/README.mustache deleted file mode 100644 index 201df14f1..000000000 --- a/generator/src/main/resources/python-nextgen-custom-client/README.mustache +++ /dev/null @@ -1,59 +0,0 @@ -# {{{projectName}}} -{{#appDescriptionWithNewLines}} -{{{.}}} -{{/appDescriptionWithNewLines}} - -This Python package is automatically generated by the [OpenAPI Generator](https://openapi-generator.tech) 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 {{{generatorLanguageVersion}}} - -## Installation & Usage -### pip install - -If the python package is hosted on a repository, you can install directly using: - -```sh -pip install git+https://{{gitHost}}/{{{gitUserId}}}/{{{gitRepoId}}}.git -``` -(you may need to run `pip` with root permission: `sudo pip install git+https://{{gitHost}}/{{{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}}} -``` - -### Tests - -Execute `pytest` to run the tests. - -## Getting Started - -Please follow the [installation procedure](#installation--usage) and then run the following: - -{{> common_README }} diff --git a/generator/src/main/resources/python-nextgen-custom-client/README_onlypackage.mustache b/generator/src/main/resources/python-nextgen-custom-client/README_onlypackage.mustache deleted file mode 100644 index d04bc051f..000000000 --- a/generator/src/main/resources/python-nextgen-custom-client/README_onlypackage.mustache +++ /dev/null @@ -1,44 +0,0 @@ -# {{{projectName}}} -{{#appDescription}} -{{{.}}} -{{/appDescription}} - -The `{{packageName}}` package is automatically generated by the [OpenAPI Generator](https://openapi-generator.tech) 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 {{{generatorLanguageVersion}}} - -## Installation & Usage - -This python library package is generated without supporting files like setup.py or requirements files - -To be able to use it, you will need these dependencies in your own package that uses this library: - -* urllib3 >= 1.25.3 -* python-dateutil -{{#asyncio}} -* aiohttp -{{/asyncio}} -{{#tornado}} -* tornado>=4.2,<5 -{{/tornado}} -* pydantic -* aenum - -## Getting Started - -In your own code, to use this library to connect and interact with {{{projectName}}}, -you can run the following: - -{{> common_README }} diff --git a/generator/src/main/resources/python-nextgen-custom-client/README_onlypackage.pebble b/generator/src/main/resources/python-nextgen-custom-client/README_onlypackage.pebble new file mode 100644 index 000000000..d268c123f --- /dev/null +++ b/generator/src/main/resources/python-nextgen-custom-client/README_onlypackage.pebble @@ -0,0 +1,44 @@ +# {{ projectName }} +{% if appDescription %} +{{ appDescription }} +{% endif %} + +The `{{ packageName }}` package is automatically generated by the [OpenAPI Generator](https://openapi-generator.tech) project: + +- API version: {{ appVersion }} +- Package version: {{ packageVersion }} +{% if not hideGenerationTimestamp %} +- Build date: {{ generatedDate }} +{% endif %} +- Build package: {{ generatorClass }} +{% if infoUrl %} +For more information, please visit [{{ infoUrl }}]({{ infoUrl }}) +{% endif %} + +## Requirements. + +Python {{ generatorLanguageVersion }} + +## Installation & Usage + +This python library package is generated without supporting files like setup.py or requirements files + +To be able to use it, you will need these dependencies in your own package that uses this library: + +* urllib3 >= 1.25.3 +* python-dateutil +{% if asyncio %} +* aiohttp +{% endif %} +{% if tornado %} +* tornado>=4.2,<5 +{% endif %} +* pydantic +* aenum + +## Getting Started + +In your own code, to use this library to connect and interact with {{ projectName }}, +you can run the following: + +{% include "common_README.pebble" %} diff --git a/generator/src/main/resources/python-nextgen-custom-client/__init__.mustache b/generator/src/main/resources/python-nextgen-custom-client/__init__.pebble similarity index 100% rename from generator/src/main/resources/python-nextgen-custom-client/__init__.mustache rename to generator/src/main/resources/python-nextgen-custom-client/__init__.pebble diff --git a/generator/src/main/resources/python-nextgen-custom-client/__init__api.mustache b/generator/src/main/resources/python-nextgen-custom-client/__init__api.mustache deleted file mode 100644 index 098ccc82b..000000000 --- a/generator/src/main/resources/python-nextgen-custom-client/__init__api.mustache +++ /dev/null @@ -1,9 +0,0 @@ -# flake8: noqa - -# import apis into api package -{{#apiInfo}}{{#apis}}from {{apiPackage}}.{{classFilename}} import {{classname}} -{{/apis}}{{/apiInfo}} - -# Async version -{{#apiInfo}}{{#apis}}from {{apiPackage}}.async_{{classFilename}} import Async{{classname}} -{{/apis}}{{/apiInfo}} diff --git a/generator/src/main/resources/python-nextgen-custom-client/__init__api.pebble b/generator/src/main/resources/python-nextgen-custom-client/__init__api.pebble new file mode 100644 index 000000000..46a4db6a1 --- /dev/null +++ b/generator/src/main/resources/python-nextgen-custom-client/__init__api.pebble @@ -0,0 +1,9 @@ +# flake8: noqa + +# import apis into api package +{% for api in apiInfo.apis %}from {{ apiPackage }}.{{ api.classFilename }} import {{ api.classname }} +{% endfor %} + +# Async version +{% for api in apiInfo.apis %}from {{ apiPackage }}.async_{{ api.classFilename }} import Async{{ api.classname }} +{% endfor %} diff --git a/generator/src/main/resources/python-nextgen-custom-client/__init__model.mustache b/generator/src/main/resources/python-nextgen-custom-client/__init__model.mustache deleted file mode 100644 index 0e1b55e2a..000000000 --- a/generator/src/main/resources/python-nextgen-custom-client/__init__model.mustache +++ /dev/null @@ -1,11 +0,0 @@ -# coding: utf-8 - -# flake8: noqa -{{>partial_header}} - -# import models into model package -{{#models}} -{{#model}} -from {{modelPackage}}.{{classFilename}} import {{classname}} -{{/model}} -{{/models}} diff --git a/generator/src/main/resources/python-nextgen-custom-client/__init__model.pebble b/generator/src/main/resources/python-nextgen-custom-client/__init__model.pebble new file mode 100644 index 000000000..0724eb620 --- /dev/null +++ b/generator/src/main/resources/python-nextgen-custom-client/__init__model.pebble @@ -0,0 +1,8 @@ +# coding: utf-8 + +# flake8: noqa +{% include "partial_header.pebble" %} + +# import models into model package +{% for modelEntry in models %}from {{ modelPackage }}.{{ modelEntry.model.classFilename }} import {{ modelEntry.model.classname }} +{% endfor -%} diff --git a/generator/src/main/resources/python-nextgen-custom-client/__init__package.mustache b/generator/src/main/resources/python-nextgen-custom-client/__init__package.mustache deleted file mode 100644 index 3b590e446..000000000 --- a/generator/src/main/resources/python-nextgen-custom-client/__init__package.mustache +++ /dev/null @@ -1,37 +0,0 @@ -# coding: utf-8 - -# flake8: noqa - -{{>partial_header}} - -# import apis into sdk package -{{#apiInfo}}{{#apis}}from {{apiPackage}}.{{classFilename}} import {{classname}} -{{/apis}}{{/apiInfo}} -{{#apiInfo}}{{#apis}}from {{apiPackage}}.async_{{classFilename}} import Async{{classname}} -{{/apis}}{{/apiInfo}} - -# import ApiClient -from {{packageName}}.api_response import ApiResponse -from {{packageName}}.api_client import ApiClient -from {{packageName}}.async_api_client import AsyncApiClient -from {{packageName}}.configuration import Configuration -from {{packageName}}.exceptions import OpenApiException -from {{packageName}}.exceptions import ApiTypeError -from {{packageName}}.exceptions import ApiValueError -from {{packageName}}.exceptions import ApiKeyError -from {{packageName}}.exceptions import ApiAttributeError -from {{packageName}}.exceptions import ApiException -{{#hasHttpSignatureMethods}} -from {{packageName}}.signing import HttpSigningConfiguration -{{/hasHttpSignatureMethods}} - -# import models into sdk package -{{#models}} -{{#model}} -from {{modelPackage}}.{{classFilename}} import {{classname}} -{{/model}} -{{/models}} -{{#recursionLimit}} - -__import__('sys').setrecursionlimit({{{.}}}) -{{/recursionLimit}} diff --git a/generator/src/main/resources/python-nextgen-custom-client/__init__package.pebble b/generator/src/main/resources/python-nextgen-custom-client/__init__package.pebble new file mode 100644 index 000000000..4d740fcdf --- /dev/null +++ b/generator/src/main/resources/python-nextgen-custom-client/__init__package.pebble @@ -0,0 +1,32 @@ +# coding: utf-8 + +# flake8: noqa + +{% include "partial_header.pebble" %} + +# import apis into sdk package +{% for api in apiInfo.apis %}from {{ apiPackage }}.{{ api.classFilename }} import {{ api.classname }} +{% endfor %} +{% for api in apiInfo.apis %}from {{ apiPackage }}.async_{{ api.classFilename }} import Async{{ api.classname }} +{% endfor %} + +# import ApiClient +from {{ packageName }}.api_response import ApiResponse +from {{ packageName }}.api_client import ApiClient +from {{ packageName }}.async_api_client import AsyncApiClient +from {{ packageName }}.configuration import Configuration +from {{ packageName }}.exceptions import OpenApiException +from {{ packageName }}.exceptions import ApiTypeError +from {{ packageName }}.exceptions import ApiValueError +from {{ packageName }}.exceptions import ApiKeyError +from {{ packageName }}.exceptions import ApiAttributeError +from {{ packageName }}.exceptions import ApiException + +# import models into sdk package +{% for modelEntry in models %}from {{ modelPackage }}.{{ modelEntry.model.classFilename }} import {{ modelEntry.model.classname }} +{% endfor -%} +{% if recursionLimit %} + +__import__('sys').setrecursionlimit({{ recursionLimit }}) +{% endif -%} + diff --git a/generator/src/main/resources/python-nextgen-custom-client/api.mustache b/generator/src/main/resources/python-nextgen-custom-client/api.mustache deleted file mode 100644 index 4097c9886..000000000 --- a/generator/src/main/resources/python-nextgen-custom-client/api.mustache +++ /dev/null @@ -1,313 +0,0 @@ -# coding: utf-8 - -{{>partial_header}} - -import re # noqa: F401 -import io - -from pydantic.v1 import validate_arguments, ValidationError -from typing_extensions import Annotated{{#asyncio}} -from typing import overload, Optional, Union, Awaitable{{/asyncio}} - -{{#imports}} -{{import}} -{{/imports}} - -from {{packageName}}.api_client import ApiClient -from {{packageName}}.api_response import ApiResponse -from {{packageName}}.exceptions import ( # noqa: F401 - ApiTypeError, - ApiValueError -) - - -{{#operations}} -class {{classname}}(object): - """NOTE: This class is auto generated by OpenAPI Generator - Ref: https://openapi-generator.tech - - Do not edit the class manually. - """ - - def __init__(self, api_client=None): - if api_client is None: - api_client = ApiClient.get_default() - self.api_client = api_client - self.line_base_path = "{{basePath}}" - -{{#operation}} - -{{#asyncio}} - @overload - async def {{operationId}}(self, {{#allParams}}{{paramName}} : {{{vendorExtensions.x-py-typing}}}{{^required}} = None{{/required}}, {{/allParams}}**kwargs) -> {{{returnType}}}{{^returnType}}None{{/returnType}}: # noqa: E501 - ... - - @overload - def {{operationId}}(self, {{#allParams}}{{paramName}} : {{{vendorExtensions.x-py-typing}}}{{^required}} = None{{/required}}, {{/allParams}}async_req: Optional[bool]=True, **kwargs) -> {{{returnType}}}{{^returnType}}None{{/returnType}}: # noqa: E501 - ... - -{{/asyncio}} - @validate_arguments - def {{operationId}}(self, {{#allParams}}{{paramName}} : {{{vendorExtensions.x-py-typing}}}{{^required}} = None{{/required}}, {{/allParams}}{{#asyncio}}async_req: Optional[bool]=None, {{/asyncio}}**kwargs) -> {{#asyncio}}Union[{{{returnType}}}{{^returnType}}None{{/returnType}}, Awaitable[{{{returnType}}}{{^returnType}}None{{/returnType}}]]{{/asyncio}}{{^asyncio}}{{{returnType}}}{{^returnType}}None{{/returnType}}{{/asyncio}}: # noqa: E501 - """{{#isDeprecated}}(Deprecated) {{/isDeprecated}}{{{summary}}}{{^summary}}{{operationId}}{{/summary}} # noqa: E501 - -{{#notes}} - {{{.}}} # noqa: E501 -{{/notes}} - This method makes a synchronous HTTP request by default. To make an - asynchronous HTTP request, please pass async_req=True - - >>> thread = api.{{operationId}}({{#allParams}}{{paramName}}, {{/allParams}}async_req=True) - >>> result = thread.get() - -{{#allParams}} - :param {{paramName}}:{{#description}} {{{.}}}{{/description}}{{#required}} (required){{/required}}{{#optional}}(optional){{/optional}} - :type {{paramName}}: {{dataType}}{{#optional}}, optional{{/optional}} -{{/allParams}} - :param async_req: Whether to execute the request asynchronously. - :type async_req: bool, optional - :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: Returns the result object. - If the method is called asynchronously, - returns the request thread. - :rtype: {{returnType}}{{^returnType}}None{{/returnType}} - """ - kwargs['_return_http_data_only'] = True - if '_preload_content' in kwargs: - raise ValueError("Error! Please call the {{operationId}}_with_http_info method with `_preload_content` instead and obtain raw data from ApiResponse.raw_data") -{{#asyncio}} - if async_req is not None: - kwargs['async_req'] = async_req -{{/asyncio}} - return self.{{operationId}}_with_http_info({{#allParams}}{{paramName}}, {{/allParams}}**kwargs) # noqa: E501 - - @validate_arguments - def {{operationId}}_with_http_info(self, {{#allParams}}{{paramName}} : {{{vendorExtensions.x-py-typing}}}{{^required}} = None{{/required}}, {{/allParams}}**kwargs) -> ApiResponse: # noqa: E501 - """{{#isDeprecated}}(Deprecated) {{/isDeprecated}}{{{summary}}}{{^summary}}{{operationId}}{{/summary}} # noqa: E501 - -{{#notes}} - {{{.}}} # noqa: E501 -{{/notes}} - This method makes a synchronous HTTP request by default. To make an - asynchronous HTTP request, please pass async_req=True - - >>> thread = api.{{operationId}}_with_http_info({{#allParams}}{{paramName}}, {{/allParams}}async_req=True) - >>> result = thread.get() - -{{#allParams}} - :param {{paramName}}:{{#description}} {{{.}}}{{/description}}{{#required}} (required){{/required}}{{#optional}}(optional){{/optional}} - :type {{paramName}}: {{dataType}}{{#optional}}, optional{{/optional}} -{{/allParams}} - :param async_req: Whether to execute the request asynchronously. - :type async_req: bool, optional - :param _preload_content: if False, the ApiResponse.data will - be set to none and raw_data will store the - HTTP response body without reading/decoding. - Default is True. - :type _preload_content: bool, optional - :param _return_http_data_only: response data instead of ApiResponse - object with status code, headers, etc - :type _return_http_data_only: bool, optional - :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. - :param _request_auth: set to override the auth_settings for an a single - request; this effectively ignores the authentication - in the spec for a single request. - :type _request_auth: dict, optional - :type _content_type: string, optional: force content-type for the request - :return: Returns the result object. - If the method is called asynchronously, - returns the request thread. - :rtype: {{#returnType}}tuple({{.}}, status_code(int), headers(HTTPHeaderDict)){{/returnType}}{{^returnType}}None{{/returnType}} - """ - - {{#isDeprecated}} - warnings.warn("{{{httpMethod}}} {{{path}}} is deprecated.", DeprecationWarning) - - {{/isDeprecated}} - {{#servers.0}} - _hosts = [ -{{#servers}} - '{{{url}}}'{{^-last}},{{/-last}} -{{/servers}} - ] - _host = _hosts[0] - if kwargs.get('_host_index'): - _host_index = int(kwargs.get('_host_index')) - if _host_index < 0 or _host_index >= len(_hosts): - raise ApiValueError( - "Invalid host index. Must be 0 <= index < %s" - % len(_host) - ) - _host = _hosts[_host_index] - {{/servers.0}} - {{^servers.0}} - _host = self.line_base_path - {{/servers.0}} - _params = locals() - - _all_params = [ -{{#allParams}} - '{{paramName}}'{{^-last}},{{/-last}} -{{/allParams}} - ] - _all_params.extend( - [ - 'async_req', - '_return_http_data_only', - '_preload_content', - '_request_timeout', - '_request_auth', - '_content_type', - '_headers' - ] - ) - - # validate the arguments - for _key, _val in _params['kwargs'].items(): - if _key not in _all_params{{#servers.0}} and _key != "_host_index"{{/servers.0}}: - raise ApiTypeError( - "Got an unexpected keyword argument '%s'" - " to method {{operationId}}" % _key - ) - _params[_key] = _val - del _params['kwargs'] - - _collection_formats = {} - - # process the path parameters - _path_params = {} -{{#pathParams}} - if _params['{{paramName}}']: - _path_params['{{baseName}}'] = _params['{{paramName}}'] - {{#isArray}} - _collection_formats['{{baseName}}'] = '{{collectionFormat}}' - {{/isArray}} - -{{/pathParams}} - - # process the query parameters - _query_params = [] -{{#queryParams}} - if _params.get('{{paramName}}') is not None: # noqa: E501 - {{#isDateTime}} - if isinstance(_params['{{paramName}}'], datetime): - _query_params.append(('{{baseName}}', _params['{{paramName}}'].strftime(self.api_client.configuration.datetime_format))) - else: - _query_params.append(('{{baseName}}', _params['{{paramName}}'])) - {{/isDateTime}} - {{^isDateTime}} - {{#isDate}} - if isinstance(_params['{{paramName}}'], datetime): - _query_params.append(('{{baseName}}', _params['{{paramName}}'].strftime(self.api_client.configuration.date_format))) - else: - _query_params.append(('{{baseName}}', _params['{{paramName}}'])) - {{/isDate}} - {{^isDate}} - _query_params.append(('{{baseName}}', _params['{{paramName}}']{{#isEnumRef}}.value{{/isEnumRef}})) - {{/isDate}} - {{/isDateTime}} - {{#isArray}} - _collection_formats['{{baseName}}'] = '{{collectionFormat}}' - {{/isArray}} - -{{/queryParams}} - # process the header parameters - _header_params = dict(_params.get('_headers', {})) -{{#headerParams}} - if _params['{{paramName}}']: - _header_params['{{baseName}}'] = _params['{{paramName}}'] - {{#isArray}} - _collection_formats['{{baseName}}'] = '{{collectionFormat}}' - {{/isArray}} - -{{/headerParams}} - # process the form parameters - _form_params = [] - _files = {} -{{#formParams}} - if _params['{{paramName}}']: - {{^isFile}} - _form_params.append(('{{{baseName}}}', _params['{{paramName}}'])) - {{/isFile}} - {{#isFile}} - _files['{{{baseName}}}'] = _params['{{paramName}}'] - {{/isFile}} - {{#isArray}} - _collection_formats['{{{baseName}}}'] = '{{collectionFormat}}' - {{/isArray}} - -{{/formParams}} - # process the body parameter - _body_params = None -{{#bodyParam}} - if _params['{{paramName}}'] is not None: - _body_params = _params['{{paramName}}'] - {{#isBinary}} - # convert to byte array if the input is a file name (str) - if isinstance(_body_params, str): - with io.open(_body_params, "rb") as _fp: - _body_params_from_file = _fp.read() - _body_params = _body_params_from_file - {{/isBinary}} - -{{/bodyParam}} - {{#hasProduces}} - # set the HTTP header `Accept` - _header_params['Accept'] = self.api_client.select_header_accept( - [{{#produces}}'{{{mediaType}}}'{{^-last}}, {{/-last}}{{/produces}}]) # noqa: E501 - - {{/hasProduces}} - {{#hasConsumes}} - # set the HTTP header `Content-Type` - _content_types_list = _params.get('_content_type', - self.api_client.select_header_content_type( - [{{#consumes}}'{{{mediaType}}}'{{^-last}}, {{/-last}}{{/consumes}}])) - if _content_types_list: - _header_params['Content-Type'] = _content_types_list - - {{/hasConsumes}} - # authentication setting - _auth_settings = [{{#authMethods}}'{{name}}'{{^-last}}, {{/-last}}{{/authMethods}}] # noqa: E501 - - {{#returnType}} - {{#responses}} - {{#-first}} - _response_types_map = { - {{/-first}} - {{^isWildcard}} - '{{code}}': {{#dataType}}"{{.}}"{{/dataType}}{{^dataType}}None{{/dataType}}, - {{/isWildcard}} - {{#-last}} - } - {{/-last}} - {{/responses}} - {{/returnType}} - {{^returnType}} - _response_types_map = {} - {{/returnType}} - - return self.api_client.call_api( - '{{{path}}}', '{{httpMethod}}', - _path_params, - _query_params, - _header_params, - body=_body_params, - post_params=_form_params, - files=_files, - response_types_map=_response_types_map, - auth_settings=_auth_settings, - async_req=_params.get('async_req'), - _return_http_data_only=_params.get('_return_http_data_only'), # noqa: E501 - _preload_content=_params.get('_preload_content', True), - _request_timeout=_params.get('_request_timeout'), - _host=_host, - collection_formats=_collection_formats, - _request_auth=_params.get('_request_auth')) -{{/operation}} -{{/operations}} diff --git a/generator/src/main/resources/python-nextgen-custom-client/api.pebble b/generator/src/main/resources/python-nextgen-custom-client/api.pebble new file mode 100644 index 000000000..07b7c1c81 --- /dev/null +++ b/generator/src/main/resources/python-nextgen-custom-client/api.pebble @@ -0,0 +1,238 @@ +# coding: utf-8 + +{% include "partial_header.pebble" %} + +import re # noqa: F401 +import io + +from pydantic.v1 import validate_arguments, ValidationError +from typing_extensions import Annotated{% if asyncio %} +from typing import overload, Optional, Union, Awaitable{% endif %} + +{% for importItem in imports %}{{ importItem.import }} +{% endfor %} +from {{ packageName }}.api_client import ApiClient +from {{ packageName }}.api_response import ApiResponse +from {{ packageName }}.exceptions import ( # noqa: F401 + ApiTypeError, + ApiValueError +) + + +class {{ operations.classname }}(object): + """NOTE: This class is auto generated by OpenAPI Generator + Ref: https://openapi-generator.tech + + Do not edit the class manually. + """ + + def __init__(self, api_client=None): + if api_client is None: + api_client = ApiClient.get_default() + self.api_client = api_client + self.line_base_path = "{{ basePath }}" + +{% for operation in operations.operation %} +{% if asyncio %} @overload + async def {{ operation.operationId }}(self, {% for param in operation.allParams %}{{ param.paramName }} : {{ param.vendorExtensions['x-py-typing'] }}{% if not param.required %} = None{% endif %}, {% endfor %}**kwargs) -> {% if operation.returnType %}{{ operation.returnType }}{% else %}None{% endif %}: # noqa: E501 + ... + + @overload + def {{ operation.operationId }}(self, {% for param in operation.allParams %}{{ param.paramName }} : {{ param.vendorExtensions['x-py-typing'] }}{% if not param.required %} = None{% endif %}, {% endfor %}async_req: Optional[bool]=True, **kwargs) -> {% if operation.returnType %}{{ operation.returnType }}{% else %}None{% endif %}: # noqa: E501 + ... + +{% endif %} @validate_arguments + def {{ operation.operationId }}(self, {% for param in operation.allParams %}{{ param.paramName }} : {{ param.vendorExtensions['x-py-typing'] }}{% if not param.required %} = None{% endif %}, {% endfor %}{% if asyncio %}async_req: Optional[bool]=None, {% endif %}**kwargs) -> {% if asyncio %}Union[{% if operation.returnType %}{{ operation.returnType }}{% else %}None{% endif %}, Awaitable[{% if operation.returnType %}{{ operation.returnType }}{% else %}None{% endif %}]]{% endif %}{% if not asyncio %}{% if operation.returnType %}{{ operation.returnType }}{% else %}None{% endif %}{% endif %}: # noqa: E501 + """{% if operation.isDeprecated %}(Deprecated) {% endif %}{% if operation.summary %}{{ operation.summary }}{% else %}{{ operation.operationId }}{% endif %} # noqa: E501 + +{% if operation.notes %} {{ operation.notes }} # noqa: E501 +{% endif %} This method makes a synchronous HTTP request by default. To make an + asynchronous HTTP request, please pass async_req=True + + >>> thread = api.{{ operation.operationId }}({% for param in operation.allParams %}{{ param.paramName }}, {% endfor %}async_req=True) + >>> result = thread.get() + +{% for param in operation.allParams %} :param {{ param.paramName }}:{% if param.description %} {{ param.description }}{% endif %}{% if param.required %} (required){% endif %}{% if param.optional %}(optional){% endif %} + :type {{ param.paramName }}: {{ param.dataType }}{% if param.optional %}, optional{% endif %} +{% endfor %} :param async_req: Whether to execute the request asynchronously. + :type async_req: bool, optional + :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: Returns the result object. + If the method is called asynchronously, + returns the request thread. + :rtype: {% if operation.returnType %}{{ operation.returnType }}{% else %}None{% endif %} + """ + kwargs['_return_http_data_only'] = True + if '_preload_content' in kwargs: + raise ValueError("Error! Please call the {{ operation.operationId }}_with_http_info method with `_preload_content` instead and obtain raw data from ApiResponse.raw_data") +{% if asyncio %} if async_req is not None: + kwargs['async_req'] = async_req +{% endif %} return self.{{ operation.operationId }}_with_http_info({% for param in operation.allParams %}{{ param.paramName }}, {% endfor %}**kwargs) # noqa: E501 + + @validate_arguments + def {{ operation.operationId }}_with_http_info(self, {% for param in operation.allParams %}{{ param.paramName }} : {{ param.vendorExtensions['x-py-typing'] }}{% if not param.required %} = None{% endif %}, {% endfor %}**kwargs) -> ApiResponse: # noqa: E501 + """{% if operation.isDeprecated %}(Deprecated) {% endif %}{% if operation.summary %}{{ operation.summary }}{% else %}{{ operation.operationId }}{% endif %} # noqa: E501 + +{% if operation.notes %} {{ operation.notes }} # noqa: E501 +{% endif %} This method makes a synchronous HTTP request by default. To make an + asynchronous HTTP request, please pass async_req=True + + >>> thread = api.{{ operation.operationId }}_with_http_info({% for param in operation.allParams %}{{ param.paramName }}, {% endfor %}async_req=True) + >>> result = thread.get() + +{% for param in operation.allParams %} :param {{ param.paramName }}:{% if param.description %} {{ param.description }}{% endif %}{% if param.required %} (required){% endif %}{% if param.optional %}(optional){% endif %} + :type {{ param.paramName }}: {{ param.dataType }}{% if param.optional %}, optional{% endif %} +{% endfor %} :param async_req: Whether to execute the request asynchronously. + :type async_req: bool, optional + :param _preload_content: if False, the ApiResponse.data will + be set to none and raw_data will store the + HTTP response body without reading/decoding. + Default is True. + :type _preload_content: bool, optional + :param _return_http_data_only: response data instead of ApiResponse + object with status code, headers, etc + :type _return_http_data_only: bool, optional + :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. + :param _request_auth: set to override the auth_settings for an a single + request; this effectively ignores the authentication + in the spec for a single request. + :type _request_auth: dict, optional + :type _content_type: string, optional: force content-type for the request + :return: Returns the result object. + If the method is called asynchronously, + returns the request thread. + :rtype: {% if operation.returnType %}tuple({{ operation.returnType }}, status_code(int), headers(HTTPHeaderDict)){% else %}None{% endif %} + """ + +{% if operation.isDeprecated %} warnings.warn("{{ operation.httpMethod }} {{ operation.path }} is deprecated.", DeprecationWarning) + +{% endif %}{% if operation.servers is not empty %} _hosts = [ +{% for server in operation.servers %} '{{ server.url }}'{% if not loop.last %},{% endif %} +{% endfor %} ] + _host = _hosts[0] + if kwargs.get('_host_index'): + _host_index = int(kwargs.get('_host_index')) + if _host_index < 0 or _host_index >= len(_hosts): + raise ApiValueError( + "Invalid host index. Must be 0 <= index < %s" + % len(_host) + ) + _host = _hosts[_host_index] +{% endif %}{% if operation.servers is empty %} _host = self.line_base_path +{% endif %} _params = locals() + + _all_params = [ +{% for param in operation.allParams %} '{{ param.paramName }}'{% if not loop.last %},{% endif %} +{% endfor %} ] + _all_params.extend( + [ + 'async_req', + '_return_http_data_only', + '_preload_content', + '_request_timeout', + '_request_auth', + '_content_type', + '_headers' + ] + ) + + # validate the arguments + for _key, _val in _params['kwargs'].items(): + if _key not in _all_params{% if operation.servers is not empty %} and _key != "_host_index"{% endif %}: + raise ApiTypeError( + "Got an unexpected keyword argument '%s'" + " to method {{ operation.operationId }}" % _key + ) + _params[_key] = _val + del _params['kwargs'] + + _collection_formats = {} + + # process the path parameters + _path_params = {} +{% for param in operation.pathParams %} if _params['{{ param.paramName }}']: + _path_params['{{ param.baseName }}'] = _params['{{ param.paramName }}'] +{% if param.isArray %} _collection_formats['{{ param.baseName }}'] = '{{ param.collectionFormat }}' +{% endif %} +{% endfor %} + # process the query parameters + _query_params = [] +{% for param in operation.queryParams %} if _params.get('{{ param.paramName }}') is not None: # noqa: E501 +{% if param.isDateTime %} if isinstance(_params['{{ param.paramName }}'], datetime): + _query_params.append(('{{ param.baseName }}', _params['{{ param.paramName }}'].strftime(self.api_client.configuration.datetime_format))) + else: + _query_params.append(('{{ param.baseName }}', _params['{{ param.paramName }}'])) +{% endif %}{% if not param.isDateTime %}{% if param.isDate %} if isinstance(_params['{{ param.paramName }}'], datetime): + _query_params.append(('{{ param.baseName }}', _params['{{ param.paramName }}'].strftime(self.api_client.configuration.date_format))) + else: + _query_params.append(('{{ param.baseName }}', _params['{{ param.paramName }}'])) +{% endif %}{% if not param.isDate %} _query_params.append(('{{ param.baseName }}', _params['{{ param.paramName }}']{% if param.isEnumRef %}.value{% endif %})) +{% endif %}{% endif %}{% if param.isArray %} _collection_formats['{{ param.baseName }}'] = '{{ param.collectionFormat }}' +{% endif %} +{% endfor %} # process the header parameters + _header_params = dict(_params.get('_headers', {})) +{% for param in operation.headerParams %} if _params['{{ param.paramName }}']: + _header_params['{{ param.baseName }}'] = _params['{{ param.paramName }}'] +{% if param.isArray %} _collection_formats['{{ param.baseName }}'] = '{{ param.collectionFormat }}' +{% endif %} +{% endfor %} # process the form parameters + _form_params = [] + _files = {} +{% for param in operation.formParams %} if _params['{{ param.paramName }}']: +{% if not param.isFile %} _form_params.append(({% verbatim %}'{% endverbatim %}{{ param.baseName }}{% verbatim %}'{% endverbatim %}, _params['{{ param.paramName }}'])) +{% endif %}{% if param.isFile %} _files[{% verbatim %}'{% endverbatim %}{{ param.baseName }}{% verbatim %}'{% endverbatim %}] = _params['{{ param.paramName }}'] +{% endif %}{% if param.isArray %} _collection_formats[{% verbatim %}'{% endverbatim %}{{ param.baseName }}{% verbatim %}'{% endverbatim %}] = '{{ param.collectionFormat }}' +{% endif %} +{% endfor %} # process the body parameter + _body_params = None +{% if operation.bodyParam is not null %} if _params['{{ operation.bodyParam.paramName }}'] is not None: + _body_params = _params['{{ operation.bodyParam.paramName }}'] +{% if operation.bodyParam.isBinary %} # convert to byte array if the input is a file name (str) + if isinstance(_body_params, str): + with io.open(_body_params, "rb") as _fp: + _body_params_from_file = _fp.read() + _body_params = _body_params_from_file +{% endif %} +{% endif %}{% if operation.hasProduces %} # set the HTTP header `Accept` + _header_params['Accept'] = self.api_client.select_header_accept( + [{% for prod in operation.produces %}'{{ prod.mediaType }}'{% if not loop.last %}, {% endif %}{% endfor %}]) # noqa: E501 + +{% endif %}{% if operation.hasConsumes %} # set the HTTP header `Content-Type` + _content_types_list = _params.get('_content_type', + self.api_client.select_header_content_type( + [{% for cons in operation.consumes %}'{{ cons.mediaType }}'{% if not loop.last %}, {% endif %}{% endfor %}])) + if _content_types_list: + _header_params['Content-Type'] = _content_types_list + +{% endif %} # authentication setting + _auth_settings = [{% for am in operation.authMethods %}'{{ am.name }}'{% if not loop.last %}, {% endif %}{% endfor %}] # noqa: E501 + +{% if operation.returnType %}{% for resp in operation.responses %}{% if loop.first %} _response_types_map = { +{% endif %}{% if not resp.isWildcard %} '{{ resp.code }}': {% if resp.dataType %}"{{ resp.dataType }}"{% else %}None{% endif %}, +{% endif %}{% if loop.last %} } +{% endif %}{% endfor %}{% endif %}{% if not operation.returnType %} _response_types_map = {} +{% endif %} + return self.api_client.call_api( + '{{ operation.path }}', '{{ operation.httpMethod }}', + _path_params, + _query_params, + _header_params, + body=_body_params, + post_params=_form_params, + files=_files, + response_types_map=_response_types_map, + auth_settings=_auth_settings, + async_req=_params.get('async_req'), + _return_http_data_only=_params.get('_return_http_data_only'), # noqa: E501 + _preload_content=_params.get('_preload_content', True), + _request_timeout=_params.get('_request_timeout'), + _host=_host, + collection_formats=_collection_formats, + _request_auth=_params.get('_request_auth')) +{% endfor -%} diff --git a/generator/src/main/resources/python-nextgen-custom-client/api_client.mustache b/generator/src/main/resources/python-nextgen-custom-client/api_client.pebble similarity index 94% rename from generator/src/main/resources/python-nextgen-custom-client/api_client.mustache rename to generator/src/main/resources/python-nextgen-custom-client/api_client.pebble index a18034849..152c6c63c 100644 --- a/generator/src/main/resources/python-nextgen-custom-client/api_client.mustache +++ b/generator/src/main/resources/python-nextgen-custom-client/api_client.pebble @@ -1,6 +1,6 @@ # coding: utf-8 -{{>partial_header}} +{% include "partial_header.pebble" %} import atexit import datetime @@ -13,15 +13,13 @@ import re import tempfile from urllib.parse import quote -{{#tornado}} -import tornado.gen -{{/tornado}} - -from {{packageName}}.configuration import Configuration -from {{packageName}}.api_response import ApiResponse -import {{modelPackage}} -from {{packageName}} import rest -from {{packageName}}.exceptions import ApiValueError, ApiException +{% if tornado %}import tornado.gen +{% endif %} +from {{ packageName }}.configuration import Configuration +from {{ packageName }}.api_response import ApiResponse +import {{ modelPackage }} +from {{ packageName }} import rest +from {{ packageName }}.exceptions import ApiValueError, ApiException from linebot.__about__ import __version__ class ApiClient(object): @@ -67,39 +65,27 @@ class ApiClient(object): self.default_headers = {} if header_name is not None: self.default_headers[header_name] = header_value - {{#authMethods}} - {{#isBasicBearer}} - self.default_headers['Authorization'] = 'Bearer {{access_token}}' + configuration.access_token - {{/isBasicBearer}} - {{^isBasicBearer}} - self.default_headers['Authorization'] = 'Basic {{access_token}}' + configuration.access_token - {{/isBasicBearer}} - {{/authMethods}} - self.cookie = cookie +{% for authMethod in authMethods %}{% if authMethod.isBasicBearer %} self.default_headers['Authorization'] = 'Bearer {{ access_token }}' + configuration.access_token +{% endif %}{% if not authMethod.isBasicBearer %} self.default_headers['Authorization'] = 'Basic {{ access_token }}' + configuration.access_token +{% endif %}{% endfor %} self.cookie = cookie # Set default User-Agent. self.user_agent = f"line-bot-sdk-python/{__version__}" self.client_side_validation = configuration.client_side_validation - {{#asyncio}} - async def __aenter__(self): +{% if asyncio %} async def __aenter__(self): return self async def __aexit__(self, exc_type, exc_value, traceback): await self.close() - {{/asyncio}} - {{^asyncio}} - def __enter__(self): +{% endif %}{% if not asyncio %} def __enter__(self): return self def __exit__(self, exc_type, exc_value, traceback): self.close() - {{/asyncio}} - - {{#asyncio}}async {{/asyncio}}def close(self): - {{#asyncio}} - await self.rest_client.close() - {{/asyncio}} - if self._pool: +{% endif %} + {% if asyncio %}async {% endif %}def close(self): +{% if asyncio %} await self.rest_client.close() +{% endif %} if self._pool: self._pool.close() self._pool.join() self._pool = None @@ -155,10 +141,8 @@ class ApiClient(object): """ cls._default = default - {{#tornado}} - @tornado.gen.coroutine - {{/tornado}} - {{#asyncio}}async {{/asyncio}}def __call_api( +{% if tornado %} @tornado.gen.coroutine +{% endif %} {% if asyncio %}async {% endif %}def __call_api( self, resource_path, method, path_params=None, query_params=None, header_params=None, body=None, post_params=None, files=None, response_types_map=None, auth_settings=None, @@ -186,7 +170,7 @@ class ApiClient(object): for k, v in path_params: # specified safe chars, encode everything resource_path = resource_path.replace( - '{%s}' % k, + {% verbatim %}'{%s}'{% endverbatim %} % k, quote(str(v), safe=config.safe_chars_for_path_param) ) @@ -224,7 +208,7 @@ class ApiClient(object): try: # perform request and return response - response_data = {{#asyncio}}await {{/asyncio}}{{#tornado}}yield {{/tornado}}self.request( + response_data = {% if asyncio %}await {% endif %}{% if tornado %}yield {% endif %}self.request( method, url, query_params=query_params, headers=header_params, @@ -261,25 +245,21 @@ class ApiClient(object): else: return_data = None -{{^tornado}} - if _return_http_data_only: +{% if not tornado %} if _return_http_data_only: return return_data else: return ApiResponse(status_code = response_data.status, data = return_data, headers = response_data.getheaders(), raw_data = response_data.data) -{{/tornado}} -{{#tornado}} - if _return_http_data_only: +{% endif %}{% if tornado %} if _return_http_data_only: raise tornado.gen.Return(return_data) else: raise tornado.gen.Return(ApiResponse(status_code = response_data.status, data = return_data, headers = response_data.getheaders(), raw_data = response_data.data)) -{{/tornado}} - +{% endif %} def sanitize_for_serialization(self, obj): """Builds a JSON POST object. @@ -368,7 +348,7 @@ class ApiClient(object): if klass in self.NATIVE_TYPES_MAPPING: klass = self.NATIVE_TYPES_MAPPING[klass] else: - klass = getattr({{modelPackage}}, klass) + klass = getattr({{ modelPackage }}, klass) if klass in self.PRIMITIVE_TYPES: return self.__deserialize_primitive(data, klass) @@ -681,15 +661,6 @@ class ApiClient(object): elif auth_setting['in'] == 'header': if auth_setting['type'] != 'http-signature': headers[auth_setting['key']] = auth_setting['value'] - {{#hasHttpSignatureMethods}} - else: - # The HTTP signature scheme requires multiple HTTP headers - # that are calculated dynamically. - signing_info = self.configuration.signing_info - auth_headers = signing_info.get_http_signature_headers( - resource_path, method, headers, body, queries) - headers.update(auth_headers) - {{/hasHttpSignatureMethods}} elif auth_setting['in'] == 'query': queries.append((auth_setting['key'], auth_setting['value'])) else: diff --git a/generator/src/main/resources/python-nextgen-custom-client/api_doc.mustache b/generator/src/main/resources/python-nextgen-custom-client/api_doc.mustache deleted file mode 100644 index 4a1d36480..000000000 --- a/generator/src/main/resources/python-nextgen-custom-client/api_doc.mustache +++ /dev/null @@ -1,74 +0,0 @@ -# {{packageName}}.{{classname}}{{#description}} -{{.}}{{/description}} - -All URIs are relative to *{{basePath}}* - -Method | HTTP request | Description -------------- | ------------- | ------------- -{{#operations}}{{#operation}}[**{{operationId}}**]({{classname}}.md#{{operationId}}) | **{{httpMethod}}** {{path}} | {{summary}} -{{/operation}}{{/operations}} - -{{#operations}} -{{#operation}} -# **{{{operationId}}}** -> {{#returnType}}{{{.}}} {{/returnType}}{{{operationId}}}({{#allParams}}{{#required}}{{{paramName}}}{{/required}}{{^required}}{{{paramName}}}={{{paramName}}}{{/required}}{{^-last}}, {{/-last}}{{/allParams}}) - -{{{summary}}}{{#notes}} - -{{{.}}}{{/notes}} - -### Example - -{{#hasAuthMethods}} -{{#authMethods}} -{{#isBasic}} -{{#isBasicBasic}} -* Basic Authentication ({{name}}): -{{/isBasicBasic}} -{{#isBasicBearer}} -* Bearer{{#bearerFormat}} ({{{.}}}){{/bearerFormat}} Authentication ({{name}}): -{{/isBasicBearer}} -{{/isBasic}} -{{#isApiKey}} -* Api Key Authentication ({{name}}): -{{/isApiKey }} -{{#isOAuth}} -* OAuth Authentication ({{name}}): -{{/isOAuth }} -{{/authMethods}} -{{/hasAuthMethods}} -{{> api_doc_example }} - -### 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}} -{{/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}}}{{^-last}}, {{/-last}}{{/consumes}}{{^consumes}}Not defined{{/consumes}} - - **Accept**: {{#produces}}{{{mediaType}}}{{^-last}}, {{/-last}}{{/produces}}{{^produces}}Not defined{{/produces}} - -{{#responses.0}} -### HTTP response details -| Status code | Description | Response headers | -|-------------|-------------|------------------| -{{#responses}} -**{{code}}** | {{message}} | {{#headers}} * {{baseName}} - {{description}}
{{/headers}}{{^headers.0}} - {{/headers.0}} | -{{/responses}} -{{/responses.0}} - -[[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/generator/src/main/resources/python-nextgen-custom-client/api_doc.pebble b/generator/src/main/resources/python-nextgen-custom-client/api_doc.pebble new file mode 100644 index 000000000..6ad310273 --- /dev/null +++ b/generator/src/main/resources/python-nextgen-custom-client/api_doc.pebble @@ -0,0 +1,52 @@ +# {{ packageName }}.{{ classname }}{% if description %} +{{ description }}{% endif %} + +All URIs are relative to *{{ basePath }}* + +Method | HTTP request | Description +------------- | ------------- | ------------- +{% for operation in operations.operation %}[**{{ operation.operationId }}**]({{ operations.classname }}.md#{{ operation.operationId }}) | **{{ operation.httpMethod }}** {{ operation.path }} | {{ operation.summary }} +{% endfor %} + +{% for operation in operations.operation %}# **{{ operation.operationId }}** +> {% if operation.returnType %}{{ operation.returnType }} {% endif %}{{ operation.operationId }}({% for param in operation.allParams %}{% if param.required %}{{ param.paramName }}{% else %}{{ param.paramName }}={{ param.paramName }}{% endif %}{% if not loop.last %}, {% endif %}{% endfor %}) + +{{ operation.summary }}{% if operation.notes %} + +{{ operation.notes }}{% endif %} + +### Example + +{% if operation.hasAuthMethods %}{% for authMethod in operation.authMethods %}{% if authMethod.isBasic %}{% if authMethod.isBasicBasic %}* Basic Authentication ({{ authMethod.name }}): +{% endif %}{% if authMethod.isBasicBearer %}* Bearer{% if authMethod.bearerFormat %} ({{ authMethod.bearerFormat }}){% endif %} Authentication ({{ authMethod.name }}): +{% endif %}{% endif %}{% if authMethod.isApiKey %}* Api Key Authentication ({{ authMethod.name }}): +{% endif %}{% if authMethod.isOAuth %}* OAuth Authentication ({{ authMethod.name }}): +{% endif %}{% endfor %}{% endif %}{% include "api_doc_example.pebble" %} + +### Parameters +{% if operation.allParams is empty %}This endpoint does not need any parameter.{% endif %}{% for param in operation.allParams %}{% if loop.last %} +Name | Type | Description | Notes +------------- | ------------- | ------------- | -------------{% endif %}{% endfor %} +{% for param in operation.allParams %} **{{ param.paramName }}** | {% if param.isFile %}**{{ param.dataType }}**{% endif %}{% if not param.isFile %}{% if param.isPrimitiveType %}**{{ param.dataType }}**{% endif %}{% if not param.isPrimitiveType %}[**{{ param.dataType }}**]({{ param.baseType }}.md){% endif %}{% endif %}| {{ param.description }} | {% if not param.required %}[optional] {% endif %}{% if param.defaultValue %}[default to {{ param.defaultValue }}]{% endif %} +{% endfor %} +### Return type + +{% if operation.returnType %}{% if operation.returnTypeIsPrimitive %}**{{ operation.returnType }}**{% else %}[**{{ operation.returnType }}**]({{ operation.returnBaseType }}.md){% endif %}{% else %}void (empty response body){% endif %} + +### Authorization + +{% if operation.authMethods is empty %}No authorization required{% endif %}{% for authMethod in operation.authMethods %}[{{ authMethod.name }}](../README.md#{{ authMethod.name }}){% if not loop.last %}, {% endif %}{% endfor %} + +### HTTP request headers + + - **Content-Type**: {% for cons in operation.consumes %}{{ cons.mediaType }}{% if not loop.last %}, {% endif %}{% endfor %}{% if operation.consumes is empty %}Not defined{% endif %} + - **Accept**: {% for prod in operation.produces %}{{ prod.mediaType }}{% if not loop.last %}, {% endif %}{% endfor %}{% if operation.produces is empty %}Not defined{% endif %} + +{% if operation.responses is not empty %}### HTTP response details +| Status code | Description | Response headers | +|-------------|-------------|------------------| +{% for resp in operation.responses %}**{{ resp.code }}** | {{ resp.message }} | {% for header in resp.headers %} * {{ header.baseName }} - {{ header.description }}
{% endfor %}{% if resp.headers is empty %} - {% endif %} | +{% endfor %} +{% endif %}[[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) + +{% endfor %} diff --git a/generator/src/main/resources/python-nextgen-custom-client/api_doc_example.mustache b/generator/src/main/resources/python-nextgen-custom-client/api_doc_example.mustache deleted file mode 100644 index d71d84365..000000000 --- a/generator/src/main/resources/python-nextgen-custom-client/api_doc_example.mustache +++ /dev/null @@ -1,31 +0,0 @@ -```python -import time -import os -import {{{packageName}}} -{{#vendorExtensions.x-py-example-import}} -{{{.}}} -{{/vendorExtensions.x-py-example-import}} -from {{{packageName}}}.rest import ApiException -from pprint import pprint - -{{> python_doc_auth_partial}} -# Enter a context with an instance of the API client -{{#asyncio}}async {{/asyncio}}with {{{packageName}}}.ApiClient(configuration) as api_client: - # Create an instance of the API class - api_instance = {{{packageName}}}.{{{classname}}}(api_client) - {{#allParams}} - {{paramName}} = {{{example}}} # {{{dataType}}} | {{{description}}}{{^required}} (optional){{/required}}{{#defaultValue}} (default to {{{.}}}){{/defaultValue}} - {{/allParams}} - - try: - {{#summary}} - # {{{.}}} - {{/summary}} - {{#returnType}}api_response = {{/returnType}}{{#asyncio}}await {{/asyncio}}api_instance.{{{operationId}}}({{#allParams}}{{#required}}{{paramName}}{{/required}}{{^required}}{{paramName}}={{paramName}}{{/required}}{{^-last}}, {{/-last}}{{/allParams}}) - {{#returnType}} - print("The response of {{classname}}->{{operationId}}:\n") - pprint(api_response) - {{/returnType}} - except Exception as e: - print("Exception when calling {{classname}}->{{operationId}}: %s\n" % e) -``` diff --git a/generator/src/main/resources/python-nextgen-custom-client/api_doc_example.pebble b/generator/src/main/resources/python-nextgen-custom-client/api_doc_example.pebble new file mode 100644 index 000000000..842d31c6a --- /dev/null +++ b/generator/src/main/resources/python-nextgen-custom-client/api_doc_example.pebble @@ -0,0 +1,23 @@ +```python +import time +import os +import {{ packageName }} +{% for imp in operation.vendorExtensions['x-py-example-import'] %}{{ imp }} +{% endfor %}from {{ packageName }}.rest import ApiException +from pprint import pprint + +{% include "python_doc_auth_partial.pebble" %} +# Enter a context with an instance of the API client +{% if asyncio %}async {% endif %}with {{ packageName }}.ApiClient(configuration) as api_client: + # Create an instance of the API class + api_instance = {{ packageName }}.{{ operations.classname }}(api_client) +{% for param in operation.allParams %} {{ param.paramName }} = {{ param.example }} # {{ param.dataType }} | {{ param.description }}{% if not param.required %} (optional){% endif %}{% if param.defaultValue %} (default to {{ param.defaultValue }}){% endif %} +{% endfor %} + try: +{% if operation.summary %} # {{ operation.summary }} +{% endif %} {% if operation.returnType %}api_response = {% endif %}{% if asyncio %}await {% endif %}api_instance.{{ operation.operationId }}({% for param in operation.allParams %}{% if param.required %}{{ param.paramName }}{% else %}{{ param.paramName }}={{ param.paramName }}{% endif %}{% if not loop.last %}, {% endif %}{% endfor %}) +{% if operation.returnType %} print("The response of {{ operations.classname }}->{{ operation.operationId }}:\n") + pprint(api_response) +{% endif %} except Exception as e: + print("Exception when calling {{ operations.classname }}->{{ operation.operationId }}: %s\n" % e) +``` diff --git a/generator/src/main/resources/python-nextgen-custom-client/api_response.mustache b/generator/src/main/resources/python-nextgen-custom-client/api_response.pebble similarity index 100% rename from generator/src/main/resources/python-nextgen-custom-client/api_response.mustache rename to generator/src/main/resources/python-nextgen-custom-client/api_response.pebble diff --git a/generator/src/main/resources/python-nextgen-custom-client/asyncio/api_client.mustache b/generator/src/main/resources/python-nextgen-custom-client/asyncio/api_client.pebble similarity index 95% rename from generator/src/main/resources/python-nextgen-custom-client/asyncio/api_client.mustache rename to generator/src/main/resources/python-nextgen-custom-client/asyncio/api_client.pebble index 09c4caedf..453a0eeb0 100644 --- a/generator/src/main/resources/python-nextgen-custom-client/asyncio/api_client.mustache +++ b/generator/src/main/resources/python-nextgen-custom-client/asyncio/api_client.pebble @@ -1,6 +1,6 @@ # coding: utf-8 -{{>partial_header}} +{% include "partial_header.pebble" %} import atexit import datetime @@ -13,15 +13,13 @@ import re import tempfile from urllib.parse import quote -{{#tornado}} -import tornado.gen -{{/tornado}} - -from {{packageName}}.configuration import Configuration -from {{packageName}}.api_response import ApiResponse -import {{modelPackage}} -from {{packageName}} import async_rest -from {{packageName}}.exceptions import ApiValueError, ApiException +{% if tornado %}import tornado.gen +{% endif %} +from {{ packageName }}.configuration import Configuration +from {{ packageName }}.api_response import ApiResponse +import {{ modelPackage }} +from {{ packageName }} import async_rest +from {{ packageName }}.exceptions import ApiValueError, ApiException from linebot.__about__ import __version__ class AsyncApiClient(object): @@ -67,15 +65,9 @@ class AsyncApiClient(object): self.default_headers = {} if header_name is not None: self.default_headers[header_name] = header_value - {{#authMethods}} - {{#isBasicBearer}} - self.default_headers['Authorization'] = 'Bearer {{access_token}}' + configuration.access_token - {{/isBasicBearer}} - {{^isBasicBearer}} - self.default_headers['Authorization'] = 'Basic {{access_token}}' + configuration.access_token - {{/isBasicBearer}} - {{/authMethods}} - self.cookie = cookie +{% for authMethod in authMethods %}{% if authMethod.isBasicBearer %} self.default_headers['Authorization'] = 'Bearer {{ access_token }}' + configuration.access_token +{% endif %}{% if not authMethod.isBasicBearer %} self.default_headers['Authorization'] = 'Basic {{ access_token }}' + configuration.access_token +{% endif %}{% endfor %} self.cookie = cookie # Set default User-Agent. self.user_agent = f"line-bot-sdk-python/{__version__}" self.client_side_validation = configuration.client_side_validation @@ -150,10 +142,8 @@ class AsyncApiClient(object): """ cls._default = default - {{#tornado}} - @tornado.gen.coroutine - {{/tornado}} - async def __call_api( +{% if tornado %} @tornado.gen.coroutine +{% endif %} async def __call_api( self, resource_path, method, path_params=None, query_params=None, header_params=None, body=None, post_params=None, files=None, response_types_map=None, auth_settings=None, @@ -181,7 +171,7 @@ class AsyncApiClient(object): for k, v in path_params: # specified safe chars, encode everything resource_path = resource_path.replace( - '{%s}' % k, + {% verbatim %}'{%s}'{% endverbatim %} % k, quote(str(v), safe=config.safe_chars_for_path_param) ) @@ -219,7 +209,7 @@ class AsyncApiClient(object): try: # perform request and return response - response_data = await {{#tornado}}yield {{/tornado}}self.request( + response_data = await {% if tornado %}yield {% endif %}self.request( method, url, query_params=query_params, headers=header_params, @@ -256,25 +246,21 @@ class AsyncApiClient(object): else: return_data = None -{{^tornado}} - if _return_http_data_only: +{% if not tornado %} if _return_http_data_only: return return_data else: return ApiResponse(status_code = response_data.status, data = return_data, headers = response_data.getheaders(), raw_data = response_data.data) -{{/tornado}} -{{#tornado}} - if _return_http_data_only: +{% endif %}{% if tornado %} if _return_http_data_only: raise tornado.gen.Return(return_data) else: raise tornado.gen.Return(ApiResponse(status_code = response_data.status, data = return_data, headers = response_data.getheaders(), raw_data = response_data.data)) -{{/tornado}} - +{% endif %} def sanitize_for_serialization(self, obj): """Builds a JSON POST object. @@ -363,7 +349,7 @@ class AsyncApiClient(object): if klass in self.NATIVE_TYPES_MAPPING: klass = self.NATIVE_TYPES_MAPPING[klass] else: - klass = getattr({{modelPackage}}, klass) + klass = getattr({{ modelPackage }}, klass) if klass in self.PRIMITIVE_TYPES: return self.__deserialize_primitive(data, klass) @@ -676,15 +662,6 @@ class AsyncApiClient(object): elif auth_setting['in'] == 'header': if auth_setting['type'] != 'http-signature': headers[auth_setting['key']] = auth_setting['value'] - {{#hasHttpSignatureMethods}} - else: - # The HTTP signature scheme requires multiple HTTP headers - # that are calculated dynamically. - signing_info = self.configuration.signing_info - auth_headers = signing_info.get_http_signature_headers( - resource_path, method, headers, body, queries) - headers.update(auth_headers) - {{/hasHttpSignatureMethods}} elif auth_setting['in'] == 'query': queries.append((auth_setting['key'], auth_setting['value'])) else: diff --git a/generator/src/main/resources/python-nextgen-custom-client/asyncio/async_api.mustache b/generator/src/main/resources/python-nextgen-custom-client/asyncio/async_api.mustache deleted file mode 100644 index 1cb0ef008..000000000 --- a/generator/src/main/resources/python-nextgen-custom-client/asyncio/async_api.mustache +++ /dev/null @@ -1,310 +0,0 @@ -# coding: utf-8 - -{{>partial_header}} - -import re # noqa: F401 -import io -import warnings - -from pydantic.v1 import validate_arguments, ValidationError -from typing_extensions import Annotated -from typing import overload, Optional, Union, Awaitable - -{{#imports}} -{{import}} -{{/imports}} - -from {{packageName}}.async_api_client import AsyncApiClient -from {{packageName}}.api_response import ApiResponse -from {{packageName}}.exceptions import ( # noqa: F401 - ApiTypeError, - ApiValueError -) - - -{{#operations}} -class Async{{classname}}(object): - """NOTE: This class is auto generated by OpenAPI Generator - Ref: https://openapi-generator.tech - - Do not edit the class manually. - """ - - def __init__(self, api_client=None): - if api_client is None: - api_client = AsyncApiClient.get_default() - self.api_client = api_client - self.line_base_path = "{{basePath}}" - -{{#operation}} - - @overload - async def {{operationId}}(self, {{#allParams}}{{paramName}} : {{{vendorExtensions.x-py-typing}}}{{^required}} = None{{/required}}, {{/allParams}}**kwargs) -> {{{returnType}}}{{^returnType}}None{{/returnType}}: # noqa: E501 - ... - - @overload - def {{operationId}}(self, {{#allParams}}{{paramName}} : {{{vendorExtensions.x-py-typing}}}{{^required}} = None{{/required}}, {{/allParams}}async_req: Optional[bool]=True, **kwargs) -> {{{returnType}}}{{^returnType}}None{{/returnType}}: # noqa: E501 - ... - - @validate_arguments - def {{operationId}}(self, {{#allParams}}{{paramName}} : {{{vendorExtensions.x-py-typing}}}{{^required}} = None{{/required}}, {{/allParams}}async_req: Optional[bool]=None, **kwargs) -> Union[{{{returnType}}}{{^returnType}}None{{/returnType}}, Awaitable[{{{returnType}}}{{^returnType}}None{{/returnType}}]]: # noqa: E501 - """{{#isDeprecated}}(Deprecated) {{/isDeprecated}}{{{summary}}}{{^summary}}{{operationId}}{{/summary}} # noqa: E501 - -{{#notes}} - {{{.}}} # noqa: E501 -{{/notes}} - This method makes a synchronous HTTP request by default. To make an - asynchronous HTTP request, please pass async_req=True - - >>> thread = api.{{operationId}}({{#allParams}}{{paramName}}, {{/allParams}}async_req=True) - >>> result = thread.get() - -{{#allParams}} - :param {{paramName}}:{{#description}} {{{.}}}{{/description}}{{#required}} (required){{/required}}{{#optional}}(optional){{/optional}} - :type {{paramName}}: {{dataType}}{{#optional}}, optional{{/optional}} -{{/allParams}} - :param async_req: Whether to execute the request asynchronously. - :type async_req: bool, optional - :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: Returns the result object. - If the method is called asynchronously, - returns the request thread. - :rtype: {{returnType}}{{^returnType}}None{{/returnType}} - """ - kwargs['_return_http_data_only'] = True - if '_preload_content' in kwargs: - raise ValueError("Error! Please call the {{operationId}}_with_http_info method with `_preload_content` instead and obtain raw data from ApiResponse.raw_data") - if async_req is not None: - kwargs['async_req'] = async_req - return self.{{operationId}}_with_http_info({{#allParams}}{{paramName}}, {{/allParams}}**kwargs) # noqa: E501 - - @validate_arguments - def {{operationId}}_with_http_info(self, {{#allParams}}{{paramName}} : {{{vendorExtensions.x-py-typing}}}{{^required}} = None{{/required}}, {{/allParams}}**kwargs) -> ApiResponse: # noqa: E501 - """{{#isDeprecated}}(Deprecated) {{/isDeprecated}}{{{summary}}}{{^summary}}{{operationId}}{{/summary}} # noqa: E501 - -{{#notes}} - {{{.}}} # noqa: E501 -{{/notes}} - This method makes a synchronous HTTP request by default. To make an - asynchronous HTTP request, please pass async_req=True - - >>> thread = api.{{operationId}}_with_http_info({{#allParams}}{{paramName}}, {{/allParams}}async_req=True) - >>> result = thread.get() - -{{#allParams}} - :param {{paramName}}:{{#description}} {{{.}}}{{/description}}{{#required}} (required){{/required}}{{#optional}}(optional){{/optional}} - :type {{paramName}}: {{dataType}}{{#optional}}, optional{{/optional}} -{{/allParams}} - :param async_req: Whether to execute the request asynchronously. - :type async_req: bool, optional - :param _preload_content: if False, the ApiResponse.data will - be set to none and raw_data will store the - HTTP response body without reading/decoding. - Default is True. - :type _preload_content: bool, optional - :param _return_http_data_only: response data instead of ApiResponse - object with status code, headers, etc - :type _return_http_data_only: bool, optional - :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. - :param _request_auth: set to override the auth_settings for an a single - request; this effectively ignores the authentication - in the spec for a single request. - :type _request_auth: dict, optional - :type _content_type: string, optional: force content-type for the request - :return: Returns the result object. - If the method is called asynchronously, - returns the request thread. - :rtype: {{#returnType}}tuple({{.}}, status_code(int), headers(HTTPHeaderDict)){{/returnType}}{{^returnType}}None{{/returnType}} - """ - - {{#isDeprecated}} - warnings.warn("{{{httpMethod}}} {{{path}}} is deprecated.", DeprecationWarning) - - {{/isDeprecated}} - {{#servers.0}} - _hosts = [ -{{#servers}} - '{{{url}}}'{{^-last}},{{/-last}} -{{/servers}} - ] - _host = _hosts[0] - if kwargs.get('_host_index'): - _host_index = int(kwargs.get('_host_index')) - if _host_index < 0 or _host_index >= len(_hosts): - raise ApiValueError( - "Invalid host index. Must be 0 <= index < %s" - % len(_host) - ) - _host = _hosts[_host_index] - {{/servers.0}} - {{^servers.0}} - _host = self.line_base_path - {{/servers.0}} - _params = locals() - - _all_params = [ -{{#allParams}} - '{{paramName}}'{{^-last}},{{/-last}} -{{/allParams}} - ] - _all_params.extend( - [ - 'async_req', - '_return_http_data_only', - '_preload_content', - '_request_timeout', - '_request_auth', - '_content_type', - '_headers' - ] - ) - - # validate the arguments - for _key, _val in _params['kwargs'].items(): - if _key not in _all_params{{#servers.0}} and _key != "_host_index"{{/servers.0}}: - raise ApiTypeError( - "Got an unexpected keyword argument '%s'" - " to method {{operationId}}" % _key - ) - _params[_key] = _val - del _params['kwargs'] - - _collection_formats = {} - - # process the path parameters - _path_params = {} -{{#pathParams}} - if _params['{{paramName}}']: - _path_params['{{baseName}}'] = _params['{{paramName}}'] - {{#isArray}} - _collection_formats['{{baseName}}'] = '{{collectionFormat}}' - {{/isArray}} - -{{/pathParams}} - - # process the query parameters - _query_params = [] -{{#queryParams}} - if _params.get('{{paramName}}') is not None: # noqa: E501 - {{#isDateTime}} - if isinstance(_params['{{paramName}}'], datetime): - _query_params.append(('{{baseName}}', _params['{{paramName}}'].strftime(self.api_client.configuration.datetime_format))) - else: - _query_params.append(('{{baseName}}', _params['{{paramName}}'])) - {{/isDateTime}} - {{^isDateTime}} - {{#isDate}} - if isinstance(_params['{{paramName}}'], datetime): - _query_params.append(('{{baseName}}', _params['{{paramName}}'].strftime(self.api_client.configuration.date_format))) - else: - _query_params.append(('{{baseName}}', _params['{{paramName}}'])) - {{/isDate}} - {{^isDate}} - _query_params.append(('{{baseName}}', _params['{{paramName}}']{{#isEnumRef}}.value{{/isEnumRef}})) - {{/isDate}} - {{/isDateTime}} - {{#isArray}} - _collection_formats['{{baseName}}'] = '{{collectionFormat}}' - {{/isArray}} - -{{/queryParams}} - # process the header parameters - _header_params = dict(_params.get('_headers', {})) -{{#headerParams}} - if _params['{{paramName}}']: - _header_params['{{baseName}}'] = _params['{{paramName}}'] - {{#isArray}} - _collection_formats['{{baseName}}'] = '{{collectionFormat}}' - {{/isArray}} - -{{/headerParams}} - # process the form parameters - _form_params = [] - _files = {} -{{#formParams}} - if _params['{{paramName}}']: - {{^isFile}} - _form_params.append(('{{{baseName}}}', _params['{{paramName}}'])) - {{/isFile}} - {{#isFile}} - _files['{{{baseName}}}'] = _params['{{paramName}}'] - {{/isFile}} - {{#isArray}} - _collection_formats['{{{baseName}}}'] = '{{collectionFormat}}' - {{/isArray}} - -{{/formParams}} - # process the body parameter - _body_params = None -{{#bodyParam}} - if _params['{{paramName}}'] is not None: - _body_params = _params['{{paramName}}'] - {{#isBinary}} - # convert to byte array if the input is a file name (str) - if isinstance(_body_params, str): - with io.open(_body_params, "rb") as _fp: - _body_params_from_file = _fp.read() - _body_params = _body_params_from_file - {{/isBinary}} - -{{/bodyParam}} - {{#hasProduces}} - # set the HTTP header `Accept` - _header_params['Accept'] = self.api_client.select_header_accept( - [{{#produces}}'{{{mediaType}}}'{{^-last}}, {{/-last}}{{/produces}}]) # noqa: E501 - - {{/hasProduces}} - {{#hasConsumes}} - # set the HTTP header `Content-Type` - _content_types_list = _params.get('_content_type', - self.api_client.select_header_content_type( - [{{#consumes}}'{{{mediaType}}}'{{^-last}}, {{/-last}}{{/consumes}}])) - if _content_types_list: - _header_params['Content-Type'] = _content_types_list - - {{/hasConsumes}} - # authentication setting - _auth_settings = [{{#authMethods}}'{{name}}'{{^-last}}, {{/-last}}{{/authMethods}}] # noqa: E501 - - {{#returnType}} - {{#responses}} - {{#-first}} - _response_types_map = { - {{/-first}} - {{^isWildcard}} - '{{code}}': {{#dataType}}"{{.}}"{{/dataType}}{{^dataType}}None{{/dataType}}, - {{/isWildcard}} - {{#-last}} - } - {{/-last}} - {{/responses}} - {{/returnType}} - {{^returnType}} - _response_types_map = {} - {{/returnType}} - - return self.api_client.call_api( - '{{{path}}}', '{{httpMethod}}', - _path_params, - _query_params, - _header_params, - body=_body_params, - post_params=_form_params, - files=_files, - response_types_map=_response_types_map, - auth_settings=_auth_settings, - async_req=_params.get('async_req'), - _return_http_data_only=_params.get('_return_http_data_only'), # noqa: E501 - _preload_content=_params.get('_preload_content', True), - _request_timeout=_params.get('_request_timeout'), - _host=_host, - collection_formats=_collection_formats, - _request_auth=_params.get('_request_auth')) -{{/operation}} -{{/operations}} diff --git a/generator/src/main/resources/python-nextgen-custom-client/asyncio/async_api.pebble b/generator/src/main/resources/python-nextgen-custom-client/asyncio/async_api.pebble new file mode 100644 index 000000000..19f531cc8 --- /dev/null +++ b/generator/src/main/resources/python-nextgen-custom-client/asyncio/async_api.pebble @@ -0,0 +1,239 @@ +# coding: utf-8 + +{% include "partial_header.pebble" %} + +import re # noqa: F401 +import io +import warnings + +from pydantic.v1 import validate_arguments, ValidationError +from typing_extensions import Annotated +from typing import overload, Optional, Union, Awaitable + +{% for importItem in imports %}{{ importItem.import }} +{% endfor %} +from {{ packageName }}.async_api_client import AsyncApiClient +from {{ packageName }}.api_response import ApiResponse +from {{ packageName }}.exceptions import ( # noqa: F401 + ApiTypeError, + ApiValueError +) + + +class Async{{ operations.classname }}(object): + """NOTE: This class is auto generated by OpenAPI Generator + Ref: https://openapi-generator.tech + + Do not edit the class manually. + """ + + def __init__(self, api_client=None): + if api_client is None: + api_client = AsyncApiClient.get_default() + self.api_client = api_client + self.line_base_path = "{{ basePath }}" + +{% for operation in operations.operation %} + @overload + async def {{ operation.operationId }}(self, {% for param in operation.allParams %}{{ param.paramName }} : {{ param.vendorExtensions['x-py-typing'] }}{% if not param.required %} = None{% endif %}, {% endfor %}**kwargs) -> {% if operation.returnType %}{{ operation.returnType }}{% else %}None{% endif %}: # noqa: E501 + ... + + @overload + def {{ operation.operationId }}(self, {% for param in operation.allParams %}{{ param.paramName }} : {{ param.vendorExtensions['x-py-typing'] }}{% if not param.required %} = None{% endif %}, {% endfor %}async_req: Optional[bool]=True, **kwargs) -> {% if operation.returnType %}{{ operation.returnType }}{% else %}None{% endif %}: # noqa: E501 + ... + + @validate_arguments + def {{ operation.operationId }}(self, {% for param in operation.allParams %}{{ param.paramName }} : {{ param.vendorExtensions['x-py-typing'] }}{% if not param.required %} = None{% endif %}, {% endfor %}async_req: Optional[bool]=None, **kwargs) -> Union[{% if operation.returnType %}{{ operation.returnType }}{% else %}None{% endif %}, Awaitable[{% if operation.returnType %}{{ operation.returnType }}{% else %}None{% endif %}]]: # noqa: E501 + """{% if operation.isDeprecated %}(Deprecated) {% endif %}{% if operation.summary %}{{ operation.summary }}{% else %}{{ operation.operationId }}{% endif %} # noqa: E501 + +{% if operation.notes %} {{ operation.notes }} # noqa: E501 +{% endif %} This method makes a synchronous HTTP request by default. To make an + asynchronous HTTP request, please pass async_req=True + + >>> thread = api.{{ operation.operationId }}({% for param in operation.allParams %}{{ param.paramName }}, {% endfor %}async_req=True) + >>> result = thread.get() + +{% for param in operation.allParams %} :param {{ param.paramName }}:{% if param.description %} {{ param.description }}{% endif %}{% if param.required %} (required){% endif %}{% if param.optional %}(optional){% endif %} + :type {{ param.paramName }}: {{ param.dataType }}{% if param.optional %}, optional{% endif %} +{% endfor %} :param async_req: Whether to execute the request asynchronously. + :type async_req: bool, optional + :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: Returns the result object. + If the method is called asynchronously, + returns the request thread. + :rtype: {% if operation.returnType %}{{ operation.returnType }}{% else %}None{% endif %} + """ + kwargs['_return_http_data_only'] = True + if '_preload_content' in kwargs: + raise ValueError("Error! Please call the {{ operation.operationId }}_with_http_info method with `_preload_content` instead and obtain raw data from ApiResponse.raw_data") + if async_req is not None: + kwargs['async_req'] = async_req + return self.{{ operation.operationId }}_with_http_info({% for param in operation.allParams %}{{ param.paramName }}, {% endfor %}**kwargs) # noqa: E501 + + @validate_arguments + def {{ operation.operationId }}_with_http_info(self, {% for param in operation.allParams %}{{ param.paramName }} : {{ param.vendorExtensions['x-py-typing'] }}{% if not param.required %} = None{% endif %}, {% endfor %}**kwargs) -> ApiResponse: # noqa: E501 + """{% if operation.isDeprecated %}(Deprecated) {% endif %}{% if operation.summary %}{{ operation.summary }}{% else %}{{ operation.operationId }}{% endif %} # noqa: E501 + +{% if operation.notes %} {{ operation.notes }} # noqa: E501 +{% endif %} This method makes a synchronous HTTP request by default. To make an + asynchronous HTTP request, please pass async_req=True + + >>> thread = api.{{ operation.operationId }}_with_http_info({% for param in operation.allParams %}{{ param.paramName }}, {% endfor %}async_req=True) + >>> result = thread.get() + +{% for param in operation.allParams %} :param {{ param.paramName }}:{% if param.description %} {{ param.description }}{% endif %}{% if param.required %} (required){% endif %}{% if param.optional %}(optional){% endif %} + :type {{ param.paramName }}: {{ param.dataType }}{% if param.optional %}, optional{% endif %} +{% endfor %} :param async_req: Whether to execute the request asynchronously. + :type async_req: bool, optional + :param _preload_content: if False, the ApiResponse.data will + be set to none and raw_data will store the + HTTP response body without reading/decoding. + Default is True. + :type _preload_content: bool, optional + :param _return_http_data_only: response data instead of ApiResponse + object with status code, headers, etc + :type _return_http_data_only: bool, optional + :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. + :param _request_auth: set to override the auth_settings for an a single + request; this effectively ignores the authentication + in the spec for a single request. + :type _request_auth: dict, optional + :type _content_type: string, optional: force content-type for the request + :return: Returns the result object. + If the method is called asynchronously, + returns the request thread. + :rtype: {% if operation.returnType %}tuple({{ operation.returnType }}, status_code(int), headers(HTTPHeaderDict)){% else %}None{% endif %} + """ + +{% if operation.isDeprecated %} warnings.warn("{{ operation.httpMethod }} {{ operation.path }} is deprecated.", DeprecationWarning) + +{% endif %}{% if operation.servers is not empty %} _hosts = [ +{% for server in operation.servers %} '{{ server.url }}'{% if not loop.last %},{% endif %} +{% endfor %} ] + _host = _hosts[0] + if kwargs.get('_host_index'): + _host_index = int(kwargs.get('_host_index')) + if _host_index < 0 or _host_index >= len(_hosts): + raise ApiValueError( + "Invalid host index. Must be 0 <= index < %s" + % len(_host) + ) + _host = _hosts[_host_index] +{% endif %}{% if operation.servers is empty %} _host = self.line_base_path +{% endif %} _params = locals() + + _all_params = [ +{% for param in operation.allParams %} '{{ param.paramName }}'{% if not loop.last %},{% endif %} +{% endfor %} ] + _all_params.extend( + [ + 'async_req', + '_return_http_data_only', + '_preload_content', + '_request_timeout', + '_request_auth', + '_content_type', + '_headers' + ] + ) + + # validate the arguments + for _key, _val in _params['kwargs'].items(): + if _key not in _all_params{% if operation.servers is not empty %} and _key != "_host_index"{% endif %}: + raise ApiTypeError( + "Got an unexpected keyword argument '%s'" + " to method {{ operation.operationId }}" % _key + ) + _params[_key] = _val + del _params['kwargs'] + + _collection_formats = {} + + # process the path parameters + _path_params = {} +{% for param in operation.pathParams %} if _params['{{ param.paramName }}']: + _path_params['{{ param.baseName }}'] = _params['{{ param.paramName }}'] +{% if param.isArray %} _collection_formats['{{ param.baseName }}'] = '{{ param.collectionFormat }}' +{% endif %} +{% endfor %} + # process the query parameters + _query_params = [] +{% for param in operation.queryParams %} if _params.get('{{ param.paramName }}') is not None: # noqa: E501 +{% if param.isDateTime %} if isinstance(_params['{{ param.paramName }}'], datetime): + _query_params.append(('{{ param.baseName }}', _params['{{ param.paramName }}'].strftime(self.api_client.configuration.datetime_format))) + else: + _query_params.append(('{{ param.baseName }}', _params['{{ param.paramName }}'])) +{% endif %}{% if not param.isDateTime %}{% if param.isDate %} if isinstance(_params['{{ param.paramName }}'], datetime): + _query_params.append(('{{ param.baseName }}', _params['{{ param.paramName }}'].strftime(self.api_client.configuration.date_format))) + else: + _query_params.append(('{{ param.baseName }}', _params['{{ param.paramName }}'])) +{% endif %}{% if not param.isDate %} _query_params.append(('{{ param.baseName }}', _params['{{ param.paramName }}']{% if param.isEnumRef %}.value{% endif %})) +{% endif %}{% endif %}{% if param.isArray %} _collection_formats['{{ param.baseName }}'] = '{{ param.collectionFormat }}' +{% endif %} +{% endfor %} # process the header parameters + _header_params = dict(_params.get('_headers', {})) +{% for param in operation.headerParams %} if _params['{{ param.paramName }}']: + _header_params['{{ param.baseName }}'] = _params['{{ param.paramName }}'] +{% if param.isArray %} _collection_formats['{{ param.baseName }}'] = '{{ param.collectionFormat }}' +{% endif %} +{% endfor %} # process the form parameters + _form_params = [] + _files = {} +{% for param in operation.formParams %} if _params['{{ param.paramName }}']: +{% if not param.isFile %} _form_params.append(({% verbatim %}'{% endverbatim %}{{ param.baseName }}{% verbatim %}'{% endverbatim %}, _params['{{ param.paramName }}'])) +{% endif %}{% if param.isFile %} _files[{% verbatim %}'{% endverbatim %}{{ param.baseName }}{% verbatim %}'{% endverbatim %}] = _params['{{ param.paramName }}'] +{% endif %}{% if param.isArray %} _collection_formats[{% verbatim %}'{% endverbatim %}{{ param.baseName }}{% verbatim %}'{% endverbatim %}] = '{{ param.collectionFormat }}' +{% endif %} +{% endfor %} # process the body parameter + _body_params = None +{% if operation.bodyParam is not null %} if _params['{{ operation.bodyParam.paramName }}'] is not None: + _body_params = _params['{{ operation.bodyParam.paramName }}'] +{% if operation.bodyParam.isBinary %} # convert to byte array if the input is a file name (str) + if isinstance(_body_params, str): + with io.open(_body_params, "rb") as _fp: + _body_params_from_file = _fp.read() + _body_params = _body_params_from_file +{% endif %} +{% endif %}{% if operation.hasProduces %} # set the HTTP header `Accept` + _header_params['Accept'] = self.api_client.select_header_accept( + [{% for prod in operation.produces %}'{{ prod.mediaType }}'{% if not loop.last %}, {% endif %}{% endfor %}]) # noqa: E501 + +{% endif %}{% if operation.hasConsumes %} # set the HTTP header `Content-Type` + _content_types_list = _params.get('_content_type', + self.api_client.select_header_content_type( + [{% for cons in operation.consumes %}'{{ cons.mediaType }}'{% if not loop.last %}, {% endif %}{% endfor %}])) + if _content_types_list: + _header_params['Content-Type'] = _content_types_list + +{% endif %} # authentication setting + _auth_settings = [{% for am in operation.authMethods %}'{{ am.name }}'{% if not loop.last %}, {% endif %}{% endfor %}] # noqa: E501 + +{% if operation.returnType %}{% for resp in operation.responses %}{% if loop.first %} _response_types_map = { +{% endif %}{% if not resp.isWildcard %} '{{ resp.code }}': {% if resp.dataType %}"{{ resp.dataType }}"{% else %}None{% endif %}, +{% endif %}{% if loop.last %} } +{% endif %}{% endfor %}{% endif %}{% if not operation.returnType %} _response_types_map = {} +{% endif %} + return self.api_client.call_api( + '{{ operation.path }}', '{{ operation.httpMethod }}', + _path_params, + _query_params, + _header_params, + body=_body_params, + post_params=_form_params, + files=_files, + response_types_map=_response_types_map, + auth_settings=_auth_settings, + async_req=_params.get('async_req'), + _return_http_data_only=_params.get('_return_http_data_only'), # noqa: E501 + _preload_content=_params.get('_preload_content', True), + _request_timeout=_params.get('_request_timeout'), + _host=_host, + collection_formats=_collection_formats, + _request_auth=_params.get('_request_auth')) +{% endfor -%} diff --git a/generator/src/main/resources/python-nextgen-custom-client/asyncio/rest.mustache b/generator/src/main/resources/python-nextgen-custom-client/asyncio/rest.pebble similarity index 98% rename from generator/src/main/resources/python-nextgen-custom-client/asyncio/rest.mustache rename to generator/src/main/resources/python-nextgen-custom-client/asyncio/rest.pebble index 98c811e51..88e983d33 100644 --- a/generator/src/main/resources/python-nextgen-custom-client/asyncio/rest.mustache +++ b/generator/src/main/resources/python-nextgen-custom-client/asyncio/rest.pebble @@ -1,6 +1,6 @@ # coding: utf-8 -{{>partial_header}} +{% include "partial_header.pebble" %} import io import json @@ -11,7 +11,7 @@ import ssl import aiohttp from urllib.parse import urlencode, quote_plus -from {{packageName}}.exceptions import ApiException, ApiValueError +from {{ packageName }}.exceptions import ApiException, ApiValueError logger = logging.getLogger(__name__) diff --git a/generator/src/main/resources/python-nextgen-custom-client/common_README.mustache b/generator/src/main/resources/python-nextgen-custom-client/common_README.mustache deleted file mode 100644 index 2691d0627..000000000 --- a/generator/src/main/resources/python-nextgen-custom-client/common_README.mustache +++ /dev/null @@ -1,82 +0,0 @@ -```python -{{#apiInfo}}{{#apis}}{{#-last}}{{#hasHttpSignatureMethods}}import datetime{{/hasHttpSignatureMethods}}{{/-last}}{{/apis}}{{/apiInfo}} -import time -import {{{packageName}}} -from {{{packageName}}}.rest import ApiException -from pprint import pprint -{{#apiInfo}}{{#apis}}{{#-first}}{{#operations}}{{#operation}}{{#-first}} -{{> python_doc_auth_partial}} - -# Enter a context with an instance of the API client -{{#asyncio}}async {{/asyncio}}with {{{packageName}}}.ApiClient(configuration) as api_client: - # Create an instance of the API class - api_instance = {{{packageName}}}.{{{classname}}}(api_client) - {{#allParams}} - {{paramName}} = {{{example}}} # {{{dataType}}} | {{{description}}}{{^required}} (optional){{/required}}{{#defaultValue}} (default to {{{.}}}){{/defaultValue}} - {{/allParams}} - - try: - {{#summary}} - # {{{.}}} - {{/summary}} - {{#returnType}}api_response = {{/returnType}}{{#asyncio}}await {{/asyncio}}api_instance.{{{operationId}}}({{#allParams}}{{#required}}{{paramName}}{{/required}}{{^required}}{{paramName}}={{paramName}}{{/required}}{{^-last}}, {{/-last}}{{/allParams}}) - {{#returnType}} - print("The response of {{classname}}->{{operationId}}:\n") - pprint(api_response) - {{/returnType}} - except ApiException as e: - print("Exception when calling {{classname}}->{{operationId}}: %s\n" % e) -{{/-first}}{{/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}} -{{/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}} -## {{{name}}} - -{{#isApiKey}} -- **Type**: API key -- **API key parameter name**: {{{keyParamName}}} -- **Location**: {{#isKeyInQuery}}URL query string{{/isKeyInQuery}}{{#isKeyInHeader}}HTTP header{{/isKeyInHeader}} -{{/isApiKey}} -{{#isBasic}} -{{#isBasicBasic}} -- **Type**: HTTP basic authentication -{{/isBasicBasic}} -{{#isBasicBearer}} -- **Type**: Bearer authentication{{#bearerFormat}} ({{{.}}}){{/bearerFormat}} -{{/isBasicBearer}} -{{/isBasic}} -{{#isOAuth}} -- **Type**: OAuth -- **Flow**: {{{flow}}} -- **Authorization URL**: {{{authorizationUrl}}} -- **Scopes**: {{^scopes}}N/A{{/scopes}} -{{#scopes}} - **{{{scope}}}**: {{{description}}} -{{/scopes}} -{{/isOAuth}} - -{{/authMethods}} - -## Author - -{{#apiInfo}}{{#apis}}{{#-last}}{{infoEmail}} -{{/-last}}{{/apis}}{{/apiInfo}} diff --git a/generator/src/main/resources/python-nextgen-custom-client/common_README.pebble b/generator/src/main/resources/python-nextgen-custom-client/common_README.pebble new file mode 100644 index 000000000..68b2eee05 --- /dev/null +++ b/generator/src/main/resources/python-nextgen-custom-client/common_README.pebble @@ -0,0 +1,81 @@ +```python +import time +import {{ packageName }} +from {{ packageName }}.rest import ApiException +from pprint import pprint +{% for api in apiInfo.apis %}{% if loop.first %}{% for opsEntry in api.operations %}{% for operation in opsEntry.operation %}{% if loop.first %} +{% include "python_doc_auth_partial.pebble" %} + +# Enter a context with an instance of the API client +{% if asyncio %}async {% endif %}with {{ packageName }}.ApiClient(configuration) as api_client: + # Create an instance of the API class + api_instance = {{ packageName }}.{{ opsEntry.classname }}(api_client) + {% for param in operation.allParams %} + {{ param.paramName }} = {{ param.example }} # {{ param.dataType }} | {{ param.description }}{% if not param.required %} (optional){% endif %}{% if param.defaultValue %} (default to {{ param.defaultValue }}){% endif %} + {% endfor %} + + try: + {% if operation.summary %} + # {{ operation.summary }} + {% endif %} + {% if operation.returnType %}api_response = {% endif %}{% if asyncio %}await {% endif %}api_instance.{{ operation.operationId }}({% for param in operation.allParams %}{% if param.required %}{{ param.paramName }}{% else %}{{ param.paramName }}={{ param.paramName }}{% endif %}{% if not loop.last %}, {% endif %}{% endfor %}) + {% if operation.returnType %} + print("The response of {{ opsEntry.classname }}->{{ operation.operationId }}:\n") + pprint(api_response) + {% endif %} + except ApiException as e: + print("Exception when calling {{ opsEntry.classname }}->{{ operation.operationId }}: %s\n" % e) +{% endif %}{% endfor %}{% endfor %}{% endif %}{% endfor %} +``` + +## Documentation for API Endpoints + +All URIs are relative to *{{ basePath }}* + +Class | Method | HTTP request | Description +------------ | ------------- | ------------- | ------------- +{% for api in apiInfo.apis %}{% for opsEntry in api.operations %}{% for operation in opsEntry.operation %}*{{ opsEntry.classname }}* | [**{{ operation.operationId }}**]({{ apiDocPath }}{{ opsEntry.classname }}.md#{{ operation.operationIdLowerCase }}) | **{{ operation.httpMethod }}** {{ operation.path }} | {{ operation.summary }} +{% endfor %}{% endfor %}{% endfor %} + +## Documentation For Models + +{% for modelEntry in models %} - [{{ modelEntry.model.classname }}]({{ modelDocPath }}{{ modelEntry.model.classname }}.md) +{% endfor %} + +## Documentation For Authorization + +{% if authMethods is empty %} + All endpoints do not require authorization. +{% endif %} +{% for authMethod in authMethods %} +{% if loop.last %} Authentication schemes defined for the API:{% endif %} +## {{ authMethod.name }} + +{% if authMethod.isApiKey %} +- **Type**: API key +- **API key parameter name**: {{ authMethod.keyParamName }} +- **Location**: {% if authMethod.isKeyInQuery %}URL query string{% endif %}{% if authMethod.isKeyInHeader %}HTTP header{% endif %} +{% endif %} +{% if authMethod.isBasic %} +{% if authMethod.isBasicBasic %} +- **Type**: HTTP basic authentication +{% endif %} +{% if authMethod.isBasicBearer %} +- **Type**: Bearer authentication{% if authMethod.bearerFormat %} ({{ authMethod.bearerFormat }}){% endif %} +{% endif %} +{% endif %} +{% if authMethod.isOAuth %} +- **Type**: OAuth +- **Flow**: {{ authMethod.flow }} +- **Authorization URL**: {{ authMethod.authorizationUrl }} +- **Scopes**: {% if authMethod.scopes is empty %}N/A{% endif %} +{% for scope in authMethod.scopes %} - **{{ scope.scope }}**: {{ scope.description }} +{% endfor %} +{% endif %} + +{% endfor %} + +## Author + +{% for api in apiInfo.apis %}{% if loop.last %}{{ api.infoEmail }} +{% endif %}{% endfor %} diff --git a/generator/src/main/resources/python-nextgen-custom-client/configuration.mustache b/generator/src/main/resources/python-nextgen-custom-client/configuration.pebble similarity index 71% rename from generator/src/main/resources/python-nextgen-custom-client/configuration.mustache rename to generator/src/main/resources/python-nextgen-custom-client/configuration.pebble index ea6c94f1e..4deaa45ea 100644 --- a/generator/src/main/resources/python-nextgen-custom-client/configuration.mustache +++ b/generator/src/main/resources/python-nextgen-custom-client/configuration.pebble @@ -1,17 +1,15 @@ # coding: utf-8 -{{>partial_header}} +{% include "partial_header.pebble" %} import copy import logging -{{^asyncio}} -import multiprocessing -{{/asyncio}} -import sys +{% if not asyncio %}import multiprocessing +{% endif %}import sys import urllib3 import http.client as httplib -from {{packageName}}.exceptions import ApiValueError +from {{ packageName }}.exceptions import ApiValueError from linebot.__about__ import __version__ JSON_SCHEMA_VALIDATION_KEYWORDS = { @@ -34,10 +32,6 @@ class Configuration(object): :param username: Username for HTTP basic authentication. :param password: Password for HTTP basic authentication. :param access_token: Access token. -{{#hasHttpSignatureMethods}} - :param signing_info: Configuration parameters for the HTTP signature security scheme. - Must be an instance of {{{packageName}}}.signing.HttpSigningConfiguration -{{/hasHttpSignatureMethods}} :param server_index: Index to servers configuration. :param server_variables: Mapping with string values to replace variables in templated server configuration. The validation of enums is performed for @@ -50,10 +44,8 @@ class Configuration(object): :param ssl_ca_cert: str - the path to a file of concatenated CA certificates in PEM format. -{{#hasAuthMethods}} - :Example: -{{#hasApiKeyMethods}} - +{% if hasAuthMethods %} :Example: +{% if hasApiKeyMethods %} API Key Authentication Example. Given the following security scheme in the OpenAPI specification: components: @@ -65,16 +57,14 @@ class Configuration(object): You can programmatically set the cookie: -conf = {{{packageName}}}.Configuration( +conf = {{ packageName }}.Configuration( api_key={'cookieAuth': 'abc123'} api_key_prefix={'cookieAuth': 'JSESSIONID'} ) The following cookie will be added to the HTTP request: Cookie: JSESSIONID abc123 -{{/hasApiKeyMethods}} -{{#hasHttpBasicMethods}} - +{% endif %}{% if hasHttpBasicMethods %} HTTP Basic Authentication Example. Given the following security scheme in the OpenAPI specification: components: @@ -85,55 +75,12 @@ conf = {{{packageName}}}.Configuration( Configure API client with HTTP basic authentication: -conf = {{{packageName}}}.Configuration( +conf = {{ packageName }}.Configuration( username='the-user', password='the-password', ) -{{/hasHttpBasicMethods}} -{{#hasHttpSignatureMethods}} - - HTTP Signature Authentication Example. - Given the following security scheme in the OpenAPI specification: - components: - securitySchemes: - http_basic_auth: - type: http - scheme: signature - - Configure API client with HTTP signature authentication. Use the 'hs2019' signature scheme, - sign the HTTP requests with the RSA-SSA-PSS signature algorithm, and set the expiration time - of the signature to 5 minutes after the signature has been created. - Note you can use the constants defined in the {{{packageName}}}.signing module, and you can - also specify arbitrary HTTP headers to be included in the HTTP signature, except for the - 'Authorization' header, which is used to carry the signature. - - One may be tempted to sign all headers by default, but in practice it rarely works. - This is because explicit proxies, transparent proxies, TLS termination endpoints or - load balancers may add/modify/remove headers. Include the HTTP headers that you know - are not going to be modified in transit. - -conf = {{{packageName}}}.Configuration( - signing_info = {{{packageName}}}.signing.HttpSigningConfiguration( - key_id = 'my-key-id', - private_key_path = 'rsa.pem', - signing_scheme = {{{packageName}}}.signing.SCHEME_HS2019, - signing_algorithm = {{{packageName}}}.signing.ALGORITHM_RSASSA_PSS, - signed_headers = [{{{packageName}}}.signing.HEADER_REQUEST_TARGET, - {{{packageName}}}.signing.HEADER_CREATED, - {{{packageName}}}.signing.HEADER_EXPIRES, - {{{packageName}}}.signing.HEADER_HOST, - {{{packageName}}}.signing.HEADER_DATE, - {{{packageName}}}.signing.HEADER_DIGEST, - 'Content-Type', - 'User-Agent' - ], - signature_max_validity = datetime.timedelta(minutes=5) - ) -) -{{/hasHttpSignatureMethods}} -{{/hasAuthMethods}} - """ +{% endif %}{% endif %} """ _default = None @@ -141,9 +88,6 @@ conf = {{{packageName}}}.Configuration( api_key=None, api_key_prefix=None, username=None, password=None, access_token=None, -{{#hasHttpSignatureMethods}} - signing_info=None, -{{/hasHttpSignatureMethods}} server_index=None, server_variables=None, server_operation_index=None, server_operation_variables=None, ssl_ca_cert=None, @@ -187,17 +131,10 @@ conf = {{{packageName}}}.Configuration( self.access_token = access_token """Access token """ -{{#hasHttpSignatureMethods}} - if signing_info is not None: - signing_info.host = host - self.signing_info = signing_info - """The HTTP signing configuration - """ -{{/hasHttpSignatureMethods}} self.logger = {} """Logging Settings """ - self.logger["package_logger"] = logging.getLogger("{{packageName}}") + self.logger["package_logger"] = logging.getLogger("{{ packageName }}") self.logger["urllib3_logger"] = logging.getLogger("urllib3") self.logger_format = '%(asctime)s %(levelname)s %(message)s' """Log format @@ -233,22 +170,18 @@ conf = {{{packageName}}}.Configuration( """Set this to True/False to enable/disable SSL hostname verification. """ - {{#asyncio}} - self.connection_pool_maxsize = 100 +{% if asyncio %} self.connection_pool_maxsize = 100 """This value is passed to the aiohttp to limit simultaneous connections. Default values is 100, None means no-limit. """ - {{/asyncio}} - {{^asyncio}} - self.connection_pool_maxsize = multiprocessing.cpu_count() * 5 +{% endif %}{% if not asyncio %} self.connection_pool_maxsize = multiprocessing.cpu_count() * 5 """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. """ - {{/asyncio}} - +{% endif %} self.proxy = None """Proxy URL """ @@ -268,11 +201,11 @@ conf = {{{packageName}}}.Configuration( """Options to pass down to the underlying urllib3 socket """ - self.datetime_format = "{{{datetimeFormat}}}" + self.datetime_format = "{{ datetimeFormat }}" """datetime format """ - self.date_format = "{{{dateFormat}}}" + self.date_format = "{{ dateFormat }}" """date format """ @@ -292,12 +225,6 @@ conf = {{{packageName}}}.Configuration( def __setattr__(self, name, value): object.__setattr__(self, name, value) -{{#hasHttpSignatureMethods}} - if name == "signing_info" and value is not None: - # Ensure the host parameter from signing info is the same as - # Configuration.host. - value.host = self.host -{{/hasHttpSignatureMethods}} @classmethod def set_default(cls, default): @@ -457,62 +384,39 @@ conf = {{{packageName}}}.Configuration( :return: The Auth Settings information dict. """ auth = {} -{{#authMethods}} -{{#isApiKey}} - if '{{name}}' in self.api_key{{#vendorExtensions.x-auth-id-alias}} or '{{.}}' in self.api_key{{/vendorExtensions.x-auth-id-alias}}: - auth['{{name}}'] = { +{% for authMethod in authMethods %}{% if authMethod.isApiKey %} if '{{ authMethod.name }}' in self.api_key{% if authMethod.vendorExtensions['x-auth-id-alias'] %} or '{{ authMethod.vendorExtensions['x-auth-id-alias'] }}' in self.api_key{% endif %}: + auth['{{ authMethod.name }}'] = { 'type': 'api_key', - 'in': {{#isKeyInCookie}}'cookie'{{/isKeyInCookie}}{{#isKeyInHeader}}'header'{{/isKeyInHeader}}{{#isKeyInQuery}}'query'{{/isKeyInQuery}}, - 'key': '{{keyParamName}}', + 'in': {% if authMethod.isKeyInCookie %}'cookie'{% endif %}{% if authMethod.isKeyInHeader %}'header'{% endif %}{% if authMethod.isKeyInQuery %}'query'{% endif %}, + 'key': '{{ authMethod.keyParamName }}', 'value': self.get_api_key_with_prefix( - '{{name}}',{{#vendorExtensions.x-auth-id-alias}} - alias='{{.}}',{{/vendorExtensions.x-auth-id-alias}} + '{{ authMethod.name }}',{% if authMethod.vendorExtensions['x-auth-id-alias'] %} + alias='{{ authMethod.vendorExtensions['x-auth-id-alias'] }}',{% endif %} ), } -{{/isApiKey}} -{{#isBasic}} - {{#isBasicBasic}} - if self.username is not None and self.password is not None: - auth['{{name}}'] = { +{% endif %}{% if authMethod.isBasic %}{% if authMethod.isBasicBasic %} if self.username is not None and self.password is not None: + auth['{{ authMethod.name }}'] = { 'type': 'basic', 'in': 'header', 'key': 'Authorization', 'value': self.get_basic_auth_token() } - {{/isBasicBasic}} - {{#isBasicBearer}} - if self.access_token is not None: - auth['{{name}}'] = { +{% endif %}{% if authMethod.isBasicBearer %} if self.access_token is not None: + auth['{{ authMethod.name }}'] = { 'type': 'bearer', 'in': 'header', - {{#bearerFormat}} - 'format': '{{{.}}}', - {{/bearerFormat}} - 'key': 'Authorization', +{% if authMethod.bearerFormat %} 'format': '{{ authMethod.bearerFormat }}', +{% endif %} 'key': 'Authorization', 'value': 'Bearer ' + self.access_token } - {{/isBasicBearer}} - {{#isHttpSignature}} - if self.signing_info is not None: - auth['{{name}}'] = { - 'type': 'http-signature', - 'in': 'header', - 'key': 'Authorization', - 'value': None # Signature headers are calculated for every HTTP request - } - {{/isHttpSignature}} -{{/isBasic}} -{{#isOAuth}} - if self.access_token is not None: - auth['{{name}}'] = { +{% endif %}{% endif %}{% if authMethod.isOAuth %} if self.access_token is not None: + auth['{{ authMethod.name }}'] = { 'type': 'oauth2', 'in': 'header', 'key': 'Authorization', 'value': 'Bearer ' + self.access_token } -{{/isOAuth}} -{{/authMethods}} - return auth +{% endif %}{% endfor %} return auth def to_debug_report(self): """Gets the essential information for debugging. @@ -522,7 +426,7 @@ conf = {{{packageName}}}.Configuration( return "Python SDK Debug Report:\n"\ "OS: {env}\n"\ "Python Version: {pyversion}\n"\ - "Version of the API: {{version}}\n"\ + "Version of the API: {{ version }}\n"\ "SDK Package Version: {__version__}".\ format(env=sys.platform, pyversion=sys.version, package_version=__version__) diff --git a/generator/src/main/resources/python-nextgen-custom-client/exceptions.mustache b/generator/src/main/resources/python-nextgen-custom-client/exceptions.pebble similarity index 99% rename from generator/src/main/resources/python-nextgen-custom-client/exceptions.mustache rename to generator/src/main/resources/python-nextgen-custom-client/exceptions.pebble index b1cecfa8b..a5db02fd6 100644 --- a/generator/src/main/resources/python-nextgen-custom-client/exceptions.mustache +++ b/generator/src/main/resources/python-nextgen-custom-client/exceptions.pebble @@ -1,6 +1,6 @@ # coding: utf-8 -{{>partial_header}} +{% include "partial_header.pebble" %} class OpenApiException(Exception): """The base exception class for all OpenAPIExceptions""" diff --git a/generator/src/main/resources/python-nextgen-custom-client/github-workflow.mustache b/generator/src/main/resources/python-nextgen-custom-client/github-workflow.mustache deleted file mode 100644 index 89c5d6285..000000000 --- a/generator/src/main/resources/python-nextgen-custom-client/github-workflow.mustache +++ /dev/null @@ -1,38 +0,0 @@ -# NOTE: This file is auto generated by OpenAPI Generator. -# URL: https://openapi-generator.tech -# -# ref: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python - -name: {{packageName}} Python package -{{=<% %>=}} - -on: [push, pull_request] - -jobs: - build: - - runs-on: ubuntu-latest - strategy: - matrix: - python-version: ["3.10"] - - steps: - - uses: actions/checkout@v3 - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v4 - with: - python-version: ${{ matrix.python-version }} - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install flake8 pytest - if [ -f requirements.txt ]; then pip install -r requirements.txt; fi - - name: Lint with flake8 - run: | - # stop the build if there are Python syntax errors or undefined names - flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics - # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide - flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics - - name: Test with pytest - run: | - pytest diff --git a/generator/src/main/resources/python-nextgen-custom-client/gitignore.mustache b/generator/src/main/resources/python-nextgen-custom-client/gitignore.mustache deleted file mode 100644 index 43995bd42..000000000 --- a/generator/src/main/resources/python-nextgen-custom-client/gitignore.mustache +++ /dev/null @@ -1,66 +0,0 @@ -# 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/ -.venv/ -.python-version -.pytest_cache - -# Translations -*.mo -*.pot - -# Django stuff: -*.log - -# Sphinx documentation -docs/_build/ - -# PyBuilder -target/ - -#Ipython Notebook -.ipynb_checkpoints diff --git a/generator/src/main/resources/python-nextgen-custom-client/model.mustache b/generator/src/main/resources/python-nextgen-custom-client/model.mustache deleted file mode 100644 index 84792dde1..000000000 --- a/generator/src/main/resources/python-nextgen-custom-client/model.mustache +++ /dev/null @@ -1,14 +0,0 @@ -# coding: utf-8 - -{{>partial_header}} - -{{#models}} -{{#model}} -{{#isEnum}} -{{>model_enum}} -{{/isEnum}} -{{^isEnum}} -{{#oneOf}}{{#-first}}{{>model_oneof}}{{/-first}}{{/oneOf}}{{^oneOf}}{{#anyOf}}{{#-first}}{{>model_anyof}}{{/-first}}{{/anyOf}}{{^anyOf}}{{>model_generic}}{{/anyOf}}{{/oneOf}} -{{/isEnum}} -{{/model}} -{{/models}} \ No newline at end of file diff --git a/generator/src/main/resources/python-nextgen-custom-client/model.pebble b/generator/src/main/resources/python-nextgen-custom-client/model.pebble new file mode 100644 index 000000000..c5f9107e6 --- /dev/null +++ b/generator/src/main/resources/python-nextgen-custom-client/model.pebble @@ -0,0 +1,5 @@ +# coding: utf-8 + +{% include "partial_header.pebble" %} + +{% for modelEntry in models %}{% set model = modelEntry.model %}{% if model.isEnum %}{% include "model_enum.pebble" %}{% endif %}{% if not model.isEnum %}{% if model.oneOf is not empty %}{% include "model_oneof.pebble" %}{% else %}{% if model.anyOf is not empty %}{% include "model_anyof.pebble" %}{% else %}{% include "model_generic.pebble" %}{% endif %}{% endif %}{% endif %}{% endfor -%} diff --git a/generator/src/main/resources/python-nextgen-custom-client/model_anyof.mustache b/generator/src/main/resources/python-nextgen-custom-client/model_anyof.mustache deleted file mode 100644 index 79a1e1ce1..000000000 --- a/generator/src/main/resources/python-nextgen-custom-client/model_anyof.mustache +++ /dev/null @@ -1,160 +0,0 @@ -from __future__ import annotations -from inspect import getfullargspec -import json -import pprint -import re # noqa: F401 -{{#vendorExtensions.x-py-datetime-imports}}{{#-first}}from datetime import{{/-first}} {{{.}}}{{^-last}},{{/-last}}{{/vendorExtensions.x-py-datetime-imports}} -{{#vendorExtensions.x-py-typing-imports}}{{#-first}}from typing import{{/-first}} {{{.}}}{{^-last}},{{/-last}}{{/vendorExtensions.x-py-typing-imports}} -{{#vendorExtensions.x-py-pydantic-imports}}{{#-first}}from pydantic.v1 import{{/-first}} {{{.}}}{{^-last}},{{/-last}}{{/vendorExtensions.x-py-pydantic-imports}} -{{#vendorExtensions.x-py-model-imports}} -{{{.}}} -{{/vendorExtensions.x-py-model-imports}} -from typing import Any, List -from pydantic.v1 import StrictStr, Field - -{{#lambda.uppercase}}{{{classname}}}{{/lambda.uppercase}}_ANY_OF_SCHEMAS = [{{#anyOf}}"{{.}}"{{^-last}}, {{/-last}}{{/anyOf}}] - -class {{classname}}({{#parent}}{{{.}}}{{/parent}}{{^parent}}BaseModel{{/parent}}): - """ - {{{description}}}{{^description}}{{{classname}}}{{/description}} - """ - -{{#composedSchemas.anyOf}} - # data type: {{{dataType}}} - {{vendorExtensions.x-py-name}}: {{{vendorExtensions.x-py-typing}}} -{{/composedSchemas.anyOf}} - actual_instance: Any - any_of_schemas: List[str] = Field({{#lambda.uppercase}}{{{classname}}}{{/lambda.uppercase}}_ANY_OF_SCHEMAS, const=True) - - class Config: - validate_assignment = True -{{#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}} - - @validator('actual_instance') - def actual_instance_must_validate_anyof(cls, v): - {{#isNullable}} - if v is None: - return v - - {{/isNullable}} - instance = cls() - error_messages = [] - {{#composedSchemas.anyOf}} - # validate data type: {{{dataType}}} - {{#isContainer}} - try: - instance.{{vendorExtensions.x-py-name}} = v - return v - except (ValidationError, ValueError) as e: - error_messages.append(str(e)) - {{/isContainer}} - {{^isContainer}} - {{#isPrimitiveType}} - try: - instance.{{vendorExtensions.x-py-name}} = v - return v - except (ValidationError, ValueError) as e: - error_messages.append(str(e)) - {{/isPrimitiveType}} - {{^isPrimitiveType}} - if not isinstance(v, {{{dataType}}}): - error_messages.append(f"Error! Input type `{type(v)}` is not `{{{dataType}}}`") - else: - return v - - {{/isPrimitiveType}} - {{/isContainer}} - {{/composedSchemas.anyOf}} - if error_messages: - # no match - raise ValueError("No match found when deserializing the JSON string into {{{classname}}} with anyOf schemas: {{#anyOf}}{{{.}}}{{^-last}}, {{/-last}}{{/anyOf}}. Details: " + ", ".join(error_messages)) - else: - return v - - @classmethod - def from_dict(cls, obj: dict) -> {{{classname}}}: - return cls.from_json(json.dumps(obj)) - - @classmethod - def from_json(cls, json_str: str) -> {{{classname}}}: - """Returns the object represented by the json string""" - instance = cls() - {{#isNullable}} - if json_str is None: - return instance - - {{/isNullable}} - error_messages = [] - {{#composedSchemas.anyOf}} - {{#isContainer}} - # deserialize data into {{{dataType}}} - try: - # validation - instance.{{vendorExtensions.x-py-name}} = json.loads(json_str) - # assign value to actual_instance - instance.actual_instance = instance.{{vendorExtensions.x-py-name}} - return instance - except (ValidationError, ValueError) as e: - error_messages.append(str(e)) - {{/isContainer}} - {{^isContainer}} - {{#isPrimitiveType}} - # deserialize data into {{{dataType}}} - try: - # validation - instance.{{vendorExtensions.x-py-name}} = json.loads(json_str) - # assign value to actual_instance - instance.actual_instance = instance.{{vendorExtensions.x-py-name}} - return instance - except (ValidationError, ValueError) as e: - error_messages.append(str(e)) - {{/isPrimitiveType}} - {{^isPrimitiveType}} - # {{vendorExtensions.x-py-name}}: {{{vendorExtensions.x-py-typing}}} - try: - instance.actual_instance = {{{dataType}}}.from_json(json_str) - return instance - except (ValidationError, ValueError) as e: - error_messages.append(str(e)) - {{/isPrimitiveType}} - {{/isContainer}} - {{/composedSchemas.anyOf}} - - if error_messages: - # no match - raise ValueError("No match found when deserializing the JSON string into {{{classname}}} with anyOf schemas: {{#anyOf}}{{{.}}}{{^-last}}, {{/-last}}{{/anyOf}}. Details: " + ", ".join(error_messages)) - else: - return instance - - def to_json(self) -> str: - """Returns the JSON representation of the actual instance""" - if self.actual_instance is None: - return "null" - - to_json = getattr(self.actual_instance, "to_json", None) - if callable(to_json): - return self.actual_instance.to_json() - else: - return json.dumps(self.actual_instance) - - def to_dict(self) -> dict: - """Returns the dict representation of the actual instance""" - if self.actual_instance is None: - return "null" - - to_json = getattr(self.actual_instance, "to_json", None) - if callable(to_json): - return self.actual_instance.to_dict() - else: - return json.dumps(self.actual_instance) - - def to_str(self) -> str: - """Returns the string representation of the actual instance""" - return pprint.pformat(self.dict()) diff --git a/generator/src/main/resources/python-nextgen-custom-client/model_anyof.pebble b/generator/src/main/resources/python-nextgen-custom-client/model_anyof.pebble new file mode 100644 index 000000000..1d39f3621 --- /dev/null +++ b/generator/src/main/resources/python-nextgen-custom-client/model_anyof.pebble @@ -0,0 +1,160 @@ +from __future__ import annotations +from inspect import getfullargspec +import json +import pprint +import re # noqa: F401 +{% if model.vendorExtensions['x-py-datetime-imports'] is not empty %}from datetime import {% for imp in model.vendorExtensions['x-py-datetime-imports'] %}{{ imp }}{% if not loop.last %},{% endif %}{% endfor %}{% endif %} +{% if model.vendorExtensions['x-py-typing-imports'] is not empty %}from typing import {% for imp in model.vendorExtensions['x-py-typing-imports'] %}{{ imp }}{% if not loop.last %},{% endif %}{% endfor %}{% endif %} +{% if model.vendorExtensions['x-py-pydantic-imports'] is not empty %}from pydantic.v1 import {% for imp in model.vendorExtensions['x-py-pydantic-imports'] %}{{ imp }}{% if not loop.last %},{% endif %}{% endfor %}{% endif %} +{% for imp in model.vendorExtensions['x-py-model-imports'] %} +{{ imp }} +{% endfor %} +from typing import Any, List +from pydantic.v1 import StrictStr, Field + +{{ model.classname | upper }}_ANY_OF_SCHEMAS = [{% for any in model.anyOf %}"{{ any }}"{% if not loop.last %}, {% endif %}{% endfor %}] + +class {{ model.classname }}({% if model.parent %}{{ model.parent }}{% else %}BaseModel{% endif %}): + """ + {% if model.description %}{{ model.description }}{% else %}{{ model.classname }}{% endif %} + """ + +{% for any in model.composedSchemas.anyOf %} + # data type: {{ any.dataType }} + {{ any.vendorExtensions['x-py-name'] }}: {{ any.vendorExtensions['x-py-typing'] }} +{% endfor %} + actual_instance: Any + any_of_schemas: List[str] = Field({{ model.classname | upper }}_ANY_OF_SCHEMAS, const=True) + + class Config: + validate_assignment = True +{% if model.discriminator is not null %} + + discriminator_value_class_map = { +{% for child in model.discriminator.children %} + '{% if not child.vendorExtensions['x-discriminator-value'] %}{{ child.name }}{% else %}{{ child.vendorExtensions['x-discriminator-value'] }}{% endif %}': '{{ child.classname }}'{% if not loop.last %},{% endif %} +{% endfor %} + } +{% endif %} + + @validator('actual_instance') + def actual_instance_must_validate_anyof(cls, v): + {% if model.isNullable %} + if v is None: + return v + + {% endif %} + instance = cls() + error_messages = [] + {% for any in model.composedSchemas.anyOf %} + # validate data type: {{ any.dataType }} + {% if any.isContainer %} + try: + instance.{{ any.vendorExtensions['x-py-name'] }} = v + return v + except (ValidationError, ValueError) as e: + error_messages.append(str(e)) + {% endif %} + {% if not any.isContainer %} + {% if any.isPrimitiveType %} + try: + instance.{{ any.vendorExtensions['x-py-name'] }} = v + return v + except (ValidationError, ValueError) as e: + error_messages.append(str(e)) + {% endif %} + {% if not any.isPrimitiveType %} + if not isinstance(v, {{ any.dataType }}): + error_messages.append(f"Error! Input type `{type(v)}` is not `{{ any.dataType }}`") + else: + return v + + {% endif %} + {% endif %} + {% endfor %} + if error_messages: + # no match + raise ValueError("No match found when deserializing the JSON string into {{ model.classname }} with anyOf schemas: {% for any in model.anyOf %}{{ any }}{% if not loop.last %}, {% endif %}{% endfor %}. Details: " + ", ".join(error_messages)) + else: + return v + + @classmethod + def from_dict(cls, obj: dict) -> {{ model.classname }}: + return cls.from_json(json.dumps(obj)) + + @classmethod + def from_json(cls, json_str: str) -> {{ model.classname }}: + """Returns the object represented by the json string""" + instance = cls() + {% if model.isNullable %} + if json_str is None: + return instance + + {% endif %} + error_messages = [] + {% for any in model.composedSchemas.anyOf %} + {% if any.isContainer %} + # deserialize data into {{ any.dataType }} + try: + # validation + instance.{{ any.vendorExtensions['x-py-name'] }} = json.loads(json_str) + # assign value to actual_instance + instance.actual_instance = instance.{{ any.vendorExtensions['x-py-name'] }} + return instance + except (ValidationError, ValueError) as e: + error_messages.append(str(e)) + {% endif %} + {% if not any.isContainer %} + {% if any.isPrimitiveType %} + # deserialize data into {{ any.dataType }} + try: + # validation + instance.{{ any.vendorExtensions['x-py-name'] }} = json.loads(json_str) + # assign value to actual_instance + instance.actual_instance = instance.{{ any.vendorExtensions['x-py-name'] }} + return instance + except (ValidationError, ValueError) as e: + error_messages.append(str(e)) + {% endif %} + {% if not any.isPrimitiveType %} + # {{ any.vendorExtensions['x-py-name'] }}: {{ any.vendorExtensions['x-py-typing'] }} + try: + instance.actual_instance = {{ any.dataType }}.from_json(json_str) + return instance + except (ValidationError, ValueError) as e: + error_messages.append(str(e)) + {% endif %} + {% endif %} + {% endfor %} + + if error_messages: + # no match + raise ValueError("No match found when deserializing the JSON string into {{ model.classname }} with anyOf schemas: {% for any in model.anyOf %}{{ any }}{% if not loop.last %}, {% endif %}{% endfor %}. Details: " + ", ".join(error_messages)) + else: + return instance + + def to_json(self) -> str: + """Returns the JSON representation of the actual instance""" + if self.actual_instance is None: + return "null" + + to_json = getattr(self.actual_instance, "to_json", None) + if callable(to_json): + return self.actual_instance.to_json() + else: + return json.dumps(self.actual_instance) + + def to_dict(self) -> dict: + """Returns the dict representation of the actual instance""" + if self.actual_instance is None: + return "null" + + to_json = getattr(self.actual_instance, "to_json", None) + if callable(to_json): + return self.actual_instance.to_dict() + else: + return json.dumps(self.actual_instance) + + def to_str(self) -> str: + """Returns the string representation of the actual instance""" + return pprint.pformat(self.dict()) diff --git a/generator/src/main/resources/python-nextgen-custom-client/model_doc.mustache b/generator/src/main/resources/python-nextgen-custom-client/model_doc.mustache deleted file mode 100644 index dd54470b3..000000000 --- a/generator/src/main/resources/python-nextgen-custom-client/model_doc.mustache +++ /dev/null @@ -1,33 +0,0 @@ -{{#models}}{{#model}}# {{classname}} - -{{#description}}{{&description}} -{{/description}} - -## Properties -Name | Type | Description | Notes ------------- | ------------- | ------------- | ------------- -{{#vars}}**{{name}}** | {{#isPrimitiveType}}**{{dataType}}**{{/isPrimitiveType}}{{^isPrimitiveType}}[**{{dataType}}**]({{complexType}}.md){{/isPrimitiveType}} | {{description}} | {{^required}}[optional] {{/required}}{{#isReadOnly}}[readonly] {{/isReadOnly}}{{#defaultValue}}[default to {{{.}}}]{{/defaultValue}} -{{/vars}} - -{{^isEnum}} -## Example - -```python -from {{modelPackage}}.{{#lambda.snakecase}}{{classname}}{{/lambda.snakecase}} import {{classname}} - -# TODO update the JSON string below -json = "{}" -# create an instance of {{classname}} from a JSON string -{{#lambda.snakecase}}{{classname}}{{/lambda.snakecase}}_instance = {{classname}}.from_json(json) -# print the JSON string representation of the object -print {{classname}}.to_json() - -# convert the object into a dict -{{#lambda.snakecase}}{{classname}}{{/lambda.snakecase}}_dict = {{#lambda.snakecase}}{{classname}}{{/lambda.snakecase}}_instance.to_dict() -# create an instance of {{classname}} from a dict -{{#lambda.snakecase}}{{classname}}{{/lambda.snakecase}}_form_dict = {{#lambda.snakecase}}{{classname}}{{/lambda.snakecase}}.from_dict({{#lambda.snakecase}}{{classname}}{{/lambda.snakecase}}_dict) -``` -{{/isEnum}} -[[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/generator/src/main/resources/python-nextgen-custom-client/model_enum.mustache b/generator/src/main/resources/python-nextgen-custom-client/model_enum.mustache deleted file mode 100644 index d9348dc12..000000000 --- a/generator/src/main/resources/python-nextgen-custom-client/model_enum.mustache +++ /dev/null @@ -1,36 +0,0 @@ -import json -import pprint -import re # noqa: F401 -from aenum import Enum, no_arg -{{#vendorExtensions.x-py-datetime-imports}}{{#-first}}from datetime import{{/-first}} {{{.}}}{{^-last}},{{/-last}}{{/vendorExtensions.x-py-datetime-imports}} -{{#vendorExtensions.x-py-typing-imports}}{{#-first}}from typing import{{/-first}} {{{.}}}{{^-last}},{{/-last}}{{/vendorExtensions.x-py-typing-imports}} -{{#vendorExtensions.x-py-pydantic-imports}}{{#-first}}from pydantic.v1 import{{/-first}} {{{.}}}{{^-last}},{{/-last}}{{/vendorExtensions.x-py-pydantic-imports}} - - -class {{classname}}({{vendorExtensions.x-py-enum-type}}, Enum): - """ - {{{description}}}{{^description}}{{{classname}}}{{/description}} - """ - - """ - allowed enum values - """ -{{#allowableValues}} - {{#enumVars}} - {{{name}}} = {{{value}}} - {{/enumVars}} - - @classmethod - def from_json(cls, json_str: str) -> {{{classname}}}: - """Create an instance of {{classname}} from a JSON string""" - return {{classname}}(json.loads(json_str)) - - {{#defaultValue}} - - # - @classmethod - def _missing_value_(cls, value): - if value is no_arg: - return cls.{{{.}}} - {{/defaultValue}} -{{/allowableValues}} diff --git a/generator/src/main/resources/python-nextgen-custom-client/model_enum.pebble b/generator/src/main/resources/python-nextgen-custom-client/model_enum.pebble new file mode 100644 index 000000000..da8eb3c97 --- /dev/null +++ b/generator/src/main/resources/python-nextgen-custom-client/model_enum.pebble @@ -0,0 +1,31 @@ +import json +import pprint +import re # noqa: F401 +from aenum import Enum, no_arg +{% if model.vendorExtensions['x-py-datetime-imports'] is not empty %}{% for imp in model.vendorExtensions['x-py-datetime-imports'] %}{% if loop.first %}from datetime import{% endif %} {{ imp }}{% if not loop.last %},{% endif %}{% endfor %}{% endif %} +{% if model.vendorExtensions['x-py-typing-imports'] is not empty %}{% for imp in model.vendorExtensions['x-py-typing-imports'] %}{% if loop.first %}from typing import{% endif %} {{ imp }}{% if not loop.last %},{% endif %}{% endfor %}{% endif %} +{% if model.vendorExtensions['x-py-pydantic-imports'] is not empty %}{% for imp in model.vendorExtensions['x-py-pydantic-imports'] %}{% if loop.first %}from pydantic.v1 import{% endif %} {{ imp }}{% if not loop.last %},{% endif %}{% endfor %}{% endif %} + + +class {{ model.classname }}({{ model.vendorExtensions['x-py-enum-type'] }}, Enum): + """ + {% if model.description %}{{ model.description }}{% else %}{{ model.classname }}{% endif %} + """ + + """ + allowed enum values + """ +{% for enumVar in model.allowableValues.enumVars %} {{ enumVar.name }} = {{ enumVar.value }} +{% endfor %} + @classmethod + def from_json(cls, json_str: str) -> {{ model.classname }}: + """Create an instance of {{ model.classname }} from a JSON string""" + return {{ model.classname }}(json.loads(json_str)) + +{% if model.defaultValue %} + # + @classmethod + def _missing_value_(cls, value): + if value is no_arg: + return cls.{{ model.defaultValue }} +{% endif %} diff --git a/generator/src/main/resources/python-nextgen-custom-client/model_generic.mustache b/generator/src/main/resources/python-nextgen-custom-client/model_generic.mustache deleted file mode 100644 index 4fa9e4d78..000000000 --- a/generator/src/main/resources/python-nextgen-custom-client/model_generic.mustache +++ /dev/null @@ -1,327 +0,0 @@ -from __future__ import annotations -import pprint -import re # noqa: F401 -import json -{{#hasChildren}} -{{#discriminator}} -import {{{modelPackage}}} -{{/discriminator}} -{{/hasChildren}} - -{{#vendorExtensions.x-py-datetime-imports}}{{#-first}}from datetime import{{/-first}} {{{.}}}{{^-last}},{{/-last}}{{/vendorExtensions.x-py-datetime-imports}} -{{#vendorExtensions.x-py-typing-imports}}{{#-first}}from typing import{{/-first}} {{{.}}}{{^-last}},{{/-last}}{{/vendorExtensions.x-py-typing-imports}} -{{#vendorExtensions.x-py-pydantic-imports}}{{#-first}}from pydantic.v1 import{{/-first}} {{{.}}}{{^-last}},{{/-last}}{{/vendorExtensions.x-py-pydantic-imports}} -{{#vendorExtensions.x-py-model-imports}} -{{{.}}} -{{/vendorExtensions.x-py-model-imports}} - -class {{classname}}({{#parent}}{{{.}}}{{/parent}}{{^parent}}BaseModel{{/parent}}): - """ - {{{description}}}{{^description}}{{{classname}}}{{/description}} - {{#externalDocumentation.url}} - {{{externalDocumentation.url}}} - {{/externalDocumentation.url}} - """ -{{#vars}} - {{name}}: {{{vendorExtensions.x-py-typing}}} -{{/vars}} -{{#vendorExtensions.x-py-type-name}} - {{{discriminator.propertyBaseName}}}: str = "{{{vendorExtensions.x-py-type-name}}}" -{{/vendorExtensions.x-py-type-name}} - -{{#isAdditionalPropertiesTrue}} - additional_properties: Dict[str, Any] = {} -{{/isAdditionalPropertiesTrue}} - __properties = [{{#allVars}}"{{baseName}}"{{^-last}}, {{/-last}}{{/allVars}}] -{{#vars}} - {{#vendorExtensions.x-regex}} - - @validator('{{{name}}}') - def {{{name}}}_validate_regular_expression(cls, value): - """Validates the regular expression""" - {{^required}} - if value is None: - return value - - {{/required}} - {{#required}} - {{#isNullable}} - if value is None: - return value - - {{/isNullable}} - {{/required}} - if not re.match(r"{{{.}}}", value{{#vendorExtensions.x-modifiers}} ,re.{{{.}}}{{/vendorExtensions.x-modifiers}}): - raise ValueError(r"must validate the regular expression {{{vendorExtensions.x-pattern}}}") - return value - {{/vendorExtensions.x-regex}} - {{#isEnum}} - - @validator('{{{name}}}') - def {{{name}}}_validate_enum(cls, value): - """Validates the enum""" - {{^required}} - if value is None: - return value - - {{/required}} - {{#required}} - {{#isNullable}} - if value is None: - return value - - {{/isNullable}} - {{/required}} - {{#isArray}} - for i in value: - if i not in ({{#allowableValues}}{{#enumVars}}{{{value}}}{{^-last}}, {{/-last}}{{/enumVars}}{{/allowableValues}}): - raise ValueError("each list item must be one of ({{#allowableValues}}{{#enumVars}}{{{value}}}{{^-last}}, {{/-last}}{{/enumVars}}{{/allowableValues}})") - {{/isArray}} - {{^isArray}} - if value not in ({{#allowableValues}}{{#enumVars}}{{{value}}}{{^-last}}, {{/-last}}{{/enumVars}}{{/allowableValues}}): - raise ValueError("must be one of enum values ({{#allowableValues}}{{#enumVars}}{{{value}}}{{^-last}}, {{/-last}}{{/enumVars}}{{/allowableValues}})") - {{/isArray}} - return value - {{/isEnum}} -{{/vars}} - - class Config: - """Pydantic configuration""" - allow_population_by_field_name = True - validate_assignment = True - -{{#hasChildren}} -{{#discriminator}} - # JSON field name that stores the object type - __discriminator_property_name = '{{discriminator.propertyBaseName}}' - - {{#mappedModels}} - {{#-first}} - # discriminator mappings - __discriminator_value_class_map = { - {{/-first}} - '{{{mappingName}}}': '{{{modelName}}}'{{^-last}},{{/-last}} - {{#-last}} - } - - @classmethod - def get_discriminator_value(cls, obj: dict) -> str: - """Returns the discriminator value (object type) of the data""" - discriminator_value = obj[cls.__discriminator_property_name] - if discriminator_value: - return cls.__discriminator_value_class_map.get(discriminator_value) - else: - return None - {{/-last}} - {{/mappedModels}} - -{{/discriminator}} -{{/hasChildren}} - def to_str(self) -> str: - """Returns the string representation of the model using alias""" - return pprint.pformat(self.dict(by_alias=True)) - - def to_json(self) -> str: - """Returns the JSON representation of the model using alias""" - return json.dumps(self.to_dict()) - - @classmethod - def from_json(cls, json_str: str) -> {{^hasChildren}}{{{classname}}}{{/hasChildren}}{{#hasChildren}}{{#discriminator}}Union({{#children}}{{{classname}}}{{^-last}}, {{/-last}}{{/children}}){{/discriminator}}{{^discriminator}}{{{classname}}}{{/discriminator}}{{/hasChildren}}: - """Create an instance of {{{classname}}} from a JSON string""" - return cls.from_dict(json.loads(json_str)) - - def to_dict(self): - """Returns the dictionary representation of the model using alias""" - _dict = self.dict(by_alias=True, - exclude={ - {{#vendorExtensions.x-py-readonly}} - "{{{.}}}", - {{/vendorExtensions.x-py-readonly}} - {{#isAdditionalPropertiesTrue}} - "additional_properties" - {{/isAdditionalPropertiesTrue}} - }, - exclude_none=True) - {{#allVars}} - {{#isContainer}} - {{#isArray}} - {{^items.isPrimitiveType}} - {{^items.isEnumOrRef}} - # override the default output from pydantic.v1 by calling `to_dict()` of each item in {{{name}}} (list) - _items = [] - if self.{{{name}}}: - for _item in self.{{{name}}}: - if _item: - _items.append(_item.to_dict()) - _dict['{{{baseName}}}'] = _items - {{/items.isEnumOrRef}} - {{/items.isPrimitiveType}} - {{/isArray}} - {{#isMap}} - {{^items.isPrimitiveType}} - {{^items.isEnumOrRef}} - # override the default output from pydantic.v1 by calling `to_dict()` of each value in {{{name}}} (dict) - _field_dict = {} - if self.{{{name}}}: - for _key in self.{{{name}}}: - if self.{{{name}}}[_key]: - _field_dict[_key] = self.{{{name}}}[_key].to_dict() - _dict['{{{baseName}}}'] = _field_dict - {{/items.isEnumOrRef}} - {{/items.isPrimitiveType}} - {{/isMap}} - {{/isContainer}} - {{^isContainer}} - {{^isPrimitiveType}} - {{^isEnumOrRef}} - # override the default output from pydantic.v1 by calling `to_dict()` of {{{name}}} - if self.{{{name}}}: - _dict['{{{baseName}}}'] = self.{{{name}}}.to_dict() - {{/isEnumOrRef}} - {{/isPrimitiveType}} - {{/isContainer}} - {{/allVars}} - {{#isAdditionalPropertiesTrue}} - # puts key-value pairs in additional_properties in the top level - if self.additional_properties is not None: - for _key, _value in self.additional_properties.items(): - _dict[_key] = _value - - {{/isAdditionalPropertiesTrue}} - {{#allVars}} - {{#isNullable}} - # set to None if {{{name}}} (nullable) is None - # and __fields_set__ contains the field - if self.{{name}} is None and "{{{name}}}" in self.__fields_set__: - _dict['{{{baseName}}}'] = None - - {{/isNullable}} - {{/allVars}} - return _dict - - @classmethod - def from_dict(cls, obj: dict) -> {{^hasChildren}}{{{classname}}}{{/hasChildren}}{{#hasChildren}}{{#discriminator}}Union({{#children}}{{{classname}}}{{^-last}}, {{/-last}}{{/children}}){{/discriminator}}{{^discriminator}}{{{classname}}}{{/discriminator}}{{/hasChildren}}: - """Create an instance of {{{classname}}} from a dict""" - {{#hasChildren}} - {{#discriminator}} - # look up the object type based on discriminator mapping - object_type = cls.get_discriminator_value(obj) - if object_type: - klass = getattr({{modelPackage}}, object_type) - return klass.from_dict(obj) - else: - raise ValueError("{{{classname}}} failed to lookup discriminator value from " + - json.dumps(obj) + ". Discriminator property name: " + cls.__discriminator_property_name + - ", mapping: " + json.dumps(cls.__discriminator_value_class_map)) - {{/discriminator}} - {{/hasChildren}} - {{^hasChildren}} - if obj is None: - return None - - if not isinstance(obj, dict): - return {{{classname}}}.parse_obj(obj) - - {{#disallowAdditionalPropertiesIfNotPresent}} - {{^isAdditionalPropertiesTrue}} - # raise errors for additional fields in the input - for _key in obj.keys(): - if _key not in cls.__properties: - raise ValueError("Error due to additional fields (not defined in {{classname}}) in the input: " + obj) - - {{/isAdditionalPropertiesTrue}} - {{/disallowAdditionalPropertiesIfNotPresent}} - _obj = {{{classname}}}.parse_obj({ - {{#allVars}} - {{#isContainer}} - {{#isArray}} - {{^items.isPrimitiveType}} - {{#items.isEnumOrRef}} - "{{{name}}}": obj.get("{{{baseName}}}"){{^-last}},{{/-last}} - {{/items.isEnumOrRef}} - {{^items.isEnumOrRef}} - "{{{name}}}": [{{{items.dataType}}}.from_dict(_item) for _item in obj.get("{{{baseName}}}")] if obj.get("{{{baseName}}}") is not None else None{{^-last}},{{/-last}} - {{/items.isEnumOrRef}} - {{/items.isPrimitiveType}} - {{#items.isPrimitiveType}} - "{{{name}}}": obj.get("{{{baseName}}}"){{^-last}},{{/-last}} - {{/items.isPrimitiveType}} - {{/isArray}} - {{#isMap}} - {{^items.isPrimitiveType}} - {{^items.isEnumOrRef}} - {{#items.isContainer}} - {{#items.isMap}} - "{{{name}}}": dict( - (_k, dict( - (_ik, {{{items.items.dataType}}}.from_dict(_iv)) - for _ik, _iv in _v.items() - ) - if _v is not None - else None - ) - for _k, _v in obj.get("{{{baseName}}}").items() - ) - if obj.get("{{{baseName}}}") is not None - else None{{^-last}},{{/-last}} - {{/items.isMap}} - {{#items.isArray}} - "{{{name}}}": dict( - (_k, [(_ik, {{{items.items.dataType}}}.from_dict(_iv))] - for _ik, _iv in _v.items() - if _v is not None - else None - ) - for _k, _v in obj.get("{{{baseName}}}").items() - ) - if obj.get("{{{baseName}}}") is not None - else None{{^-last}},{{/-last}} - {{/items.isArray}} - {{/items.isContainer}} - {{^items.isContainer}} - "{{{name}}}": dict( - (_k, {{{items.dataType}}}.from_dict(_v)) - for _k, _v in obj.get("{{{baseName}}}").items() - ) - if obj.get("{{{baseName}}}") is not None - else None{{^-last}},{{/-last}} - {{/items.isContainer}} - {{/items.isEnumOrRef}} - {{#items.isEnumOrRef}} - "{{{name}}}": dict((_k, _v) for _k, _v in obj.get("{{{baseName}}}").items()){{^-last}},{{/-last}} - {{/items.isEnumOrRef}} - {{/items.isPrimitiveType}} - {{#items.isPrimitiveType}} - "{{{name}}}": obj.get("{{{baseName}}}"){{^-last}},{{/-last}} - {{/items.isPrimitiveType}} - {{/isMap}} - {{/isContainer}} - {{^isContainer}} - {{^isPrimitiveType}} - {{^isEnumOrRef}} - "{{{name}}}": {{{dataType}}}.from_dict(obj.get("{{{baseName}}}")) if obj.get("{{{baseName}}}") is not None else None{{^-last}},{{/-last}} - {{/isEnumOrRef}} - {{#isEnumOrRef}} - "{{{name}}}": obj.get("{{{baseName}}}"){{^-last}},{{/-last}} - {{/isEnumOrRef}} - {{/isPrimitiveType}} - {{#isPrimitiveType}} - {{#defaultValue}} - "{{{name}}}": obj.get("{{{baseName}}}") if obj.get("{{{baseName}}}") is not None else {{{defaultValue}}}{{^-last}},{{/-last}} - {{/defaultValue}} - {{^defaultValue}} - "{{{name}}}": obj.get("{{{baseName}}}"){{^-last}},{{/-last}} - {{/defaultValue}} - {{/isPrimitiveType}} - {{/isContainer}} - {{/allVars}} - }) - {{#isAdditionalPropertiesTrue}} - # store additional fields in additional_properties - for _key in obj.keys(): - if _key not in cls.__properties: - _obj.additional_properties[_key] = obj.get(_key) - - {{/isAdditionalPropertiesTrue}} - return _obj - {{/hasChildren}} diff --git a/generator/src/main/resources/python-nextgen-custom-client/model_generic.pebble b/generator/src/main/resources/python-nextgen-custom-client/model_generic.pebble new file mode 100644 index 000000000..1c94ec322 --- /dev/null +++ b/generator/src/main/resources/python-nextgen-custom-client/model_generic.pebble @@ -0,0 +1,192 @@ +from __future__ import annotations +import pprint +import re # noqa: F401 +import json +{% if model.hasChildren %}{% if model.discriminator is not null %}import {{ modelPackage }} +{% endif %}{% endif %} +{% if model.vendorExtensions['x-py-datetime-imports'] is not empty %}{% for imp in model.vendorExtensions['x-py-datetime-imports'] %}{% if loop.first %}from datetime import{% endif %} {{ imp }}{% if not loop.last %},{% endif %}{% endfor %}{% endif %} +{% if model.vendorExtensions['x-py-typing-imports'] is not empty %}{% for imp in model.vendorExtensions['x-py-typing-imports'] %}{% if loop.first %}from typing import{% endif %} {{ imp }}{% if not loop.last %},{% endif %}{% endfor %}{% endif %} +{% if model.vendorExtensions['x-py-pydantic-imports'] is not empty %}{% for imp in model.vendorExtensions['x-py-pydantic-imports'] %}{% if loop.first %}from pydantic.v1 import{% endif %} {{ imp }}{% if not loop.last %},{% endif %}{% endfor %}{% endif %} +{% for imp in model.vendorExtensions['x-py-model-imports'] %}{{ imp }} +{% endfor %} +class {{ model.classname }}({% if model.parent %}{{ model.parent }}{% else %}BaseModel{% endif %}): + """ + {% if model.description %}{{ model.description }}{% else %}{{ model.classname }}{% endif %} +{% if model.externalDocumentation.url %} {{ model.externalDocumentation.url }} +{% endif %} """ +{% for var in model.vars %} {{ var.name }}: {{ var.vendorExtensions['x-py-typing'] }} +{% endfor %}{% if model.vendorExtensions['x-py-type-name'] %} {{ model.discriminator.propertyBaseName }}: str = "{{ model.vendorExtensions['x-py-type-name'] }}" +{% endif %} +{% if model.isAdditionalPropertiesTrue %} additional_properties: Dict[str, Any] = {} +{% endif %} __properties = [{% for var in model.allVars %}"{{ var.baseName }}"{% if not loop.last %}, {% endif %}{% endfor %}] +{% for var in model.vars %}{% if var.vendorExtensions['x-regex'] %} + @validator('{{ var.name }}') + def {{ var.name }}_validate_regular_expression(cls, value): + """Validates the regular expression""" +{% if not var.required %} if value is None: + return value + +{% endif %}{% if var.required %}{% if var.isNullable %} if value is None: + return value + +{% endif %}{% endif %} if not re.match(r"{{ var.vendorExtensions['x-regex'] }}", value{% for mod in var.vendorExtensions['x-modifiers'] %} ,re.{{ mod }}{% endfor %}): + raise ValueError(r"must validate the regular expression {{ var.vendorExtensions['x-pattern'] }}") + return value +{% endif %}{% if var.isEnum %} + @validator('{{ var.name }}') + def {{ var.name }}_validate_enum(cls, value): + """Validates the enum""" +{% if not var.required %} if value is None: + return value + +{% endif %}{% if var.required %}{% if var.isNullable %} if value is None: + return value + +{% endif %}{% endif %}{% if var.isArray %} for i in value: + if i not in ({% for ev in var.allowableValues.enumVars %}{{ ev.value }}{% if not loop.last %}, {% endif %}{% endfor %}): + raise ValueError("each list item must be one of ({% for ev in var.allowableValues.enumVars %}{{ ev.value }}{% if not loop.last %}, {% endif %}{% endfor %})") +{% endif %}{% if not var.isArray %} if value not in ({% for ev in var.allowableValues.enumVars %}{{ ev.value }}{% if not loop.last %}, {% endif %}{% endfor %}): + raise ValueError("must be one of enum values ({% for ev in var.allowableValues.enumVars %}{{ ev.value }}{% if not loop.last %}, {% endif %}{% endfor %})") +{% endif %} return value +{% endif %}{% endfor %} + class Config: + """Pydantic configuration""" + allow_population_by_field_name = True + validate_assignment = True + +{% if model.hasChildren %}{% if model.discriminator is not null %} # JSON field name that stores the object type + __discriminator_property_name = '{{ model.discriminator.propertyBaseName }}' + +{% for mapped in model.discriminator.mappedModels %}{% if loop.first %} # discriminator mappings + __discriminator_value_class_map = { +{% endif %} '{{ mapped.mappingName }}': '{{ mapped.modelName }}'{% if not loop.last %},{% endif %} +{% if loop.last %} } + + @classmethod + def get_discriminator_value(cls, obj: dict) -> str: + """Returns the discriminator value (object type) of the data""" + discriminator_value = obj[cls.__discriminator_property_name] + if discriminator_value: + return cls.__discriminator_value_class_map.get(discriminator_value) + else: + return None +{% endif %}{% endfor %} +{% endif %}{% endif %} def to_str(self) -> str: + """Returns the string representation of the model using alias""" + return pprint.pformat(self.dict(by_alias=True)) + + def to_json(self) -> str: + """Returns the JSON representation of the model using alias""" + return json.dumps(self.to_dict()) + + @classmethod + def from_json(cls, json_str: str) -> {% if not model.hasChildren %}{{ model.classname }}{% endif %}{% if model.hasChildren %}{% if model.discriminator is not null %}Union({% for child in model.children %}{{ child.classname }}{% if not loop.last %}, {% endif %}{% endfor %}){% else %}{{ model.classname }}{% endif %}{% endif %}: + """Create an instance of {{ model.classname }} from a JSON string""" + return cls.from_dict(json.loads(json_str)) + + def to_dict(self): + """Returns the dictionary representation of the model using alias""" + _dict = self.dict(by_alias=True, + exclude={ +{% for ro in model.vendorExtensions['x-py-readonly'] %} "{{ ro }}", +{% endfor %}{% if model.isAdditionalPropertiesTrue %} "additional_properties" +{% endif %} }, + exclude_none=True) +{% for var in model.allVars %}{% if var.isContainer %}{% if var.isArray %}{% if not var.items.isPrimitiveType %}{% if not var.items.isEnumOrRef %} # override the default output from pydantic.v1 by calling `to_dict()` of each item in {{ var.name }} (list) + _items = [] + if self.{{ var.name }}: + for _item in self.{{ var.name }}: + if _item: + _items.append(_item.to_dict()) + _dict['{{ var.baseName }}'] = _items +{% endif %}{% endif %}{% endif %}{% if var.isMap %}{% if not var.items.isPrimitiveType %}{% if not var.items.isEnumOrRef %} # override the default output from pydantic.v1 by calling `to_dict()` of each value in {{ var.name }} (dict) + _field_dict = {} + if self.{{ var.name }}: + for _key in self.{{ var.name }}: + if self.{{ var.name }}[_key]: + _field_dict[_key] = self.{{ var.name }}[_key].to_dict() + _dict['{{ var.baseName }}'] = _field_dict +{% endif %}{% endif %}{% endif %}{% endif %}{% if not var.isContainer %}{% if not var.isPrimitiveType %}{% if not var.isEnumOrRef %} # override the default output from pydantic.v1 by calling `to_dict()` of {{ var.name }} + if self.{{ var.name }}: + _dict['{{ var.baseName }}'] = self.{{ var.name }}.to_dict() +{% endif %}{% endif %}{% endif %}{% endfor %}{% if model.isAdditionalPropertiesTrue %} # puts key-value pairs in additional_properties in the top level + if self.additional_properties is not None: + for _key, _value in self.additional_properties.items(): + _dict[_key] = _value + +{% endif %}{% for var in model.allVars %}{% if var.isNullable %} # set to None if {{ var.name }} (nullable) is None + # and __fields_set__ contains the field + if self.{{ var.name }} is None and "{{ var.name }}" in self.__fields_set__: + _dict['{{ var.baseName }}'] = None + +{% endif %}{% endfor %} return _dict + + @classmethod + def from_dict(cls, obj: dict) -> {% if not model.hasChildren %}{{ model.classname }}{% endif %}{% if model.hasChildren %}{% if model.discriminator is not null %}Union({% for child in model.children %}{{ child.classname }}{% if not loop.last %}, {% endif %}{% endfor %}){% else %}{{ model.classname }}{% endif %}{% endif %}: + """Create an instance of {{ model.classname }} from a dict""" +{% if model.hasChildren %}{% if model.discriminator is not null %} # look up the object type based on discriminator mapping + object_type = cls.get_discriminator_value(obj) + if object_type: + klass = getattr({{ modelPackage }}, object_type) + return klass.from_dict(obj) + else: + raise ValueError("{{ model.classname }} failed to lookup discriminator value from " + + json.dumps(obj) + ". Discriminator property name: " + cls.__discriminator_property_name + + ", mapping: " + json.dumps(cls.__discriminator_value_class_map)) +{% endif %}{% endif %}{% if not model.hasChildren %} if obj is None: + return None + + if not isinstance(obj, dict): + return {{ model.classname }}.parse_obj(obj) + +{% if disallowAdditionalPropertiesIfNotPresent %}{% if not model.isAdditionalPropertiesTrue %} # raise errors for additional fields in the input + for _key in obj.keys(): + if _key not in cls.__properties: + raise ValueError("Error due to additional fields (not defined in {{ model.classname }}) in the input: " + obj) + +{% endif %}{% endif %} _obj = {{ model.classname }}.parse_obj({ +{% for var in model.allVars %}{% if var.isContainer %}{% if var.isArray %}{% if not var.items.isPrimitiveType %}{% if var.items.isEnumOrRef %} "{{ var.name }}": obj.get("{{ var.baseName }}"){% if not loop.last %},{% endif %} +{% endif %}{% if not var.items.isEnumOrRef %} "{{ var.name }}": [{{ var.items.dataType }}.from_dict(_item) for _item in obj.get("{{ var.baseName }}")] if obj.get("{{ var.baseName }}") is not None else None{% if not loop.last %},{% endif %} +{% endif %}{% endif %}{% if var.items.isPrimitiveType %} "{{ var.name }}": obj.get("{{ var.baseName }}"){% if not loop.last %},{% endif %} +{% endif %}{% endif %}{% if var.isMap %}{% if not var.items.isPrimitiveType %}{% if not var.items.isEnumOrRef %}{% if var.items.isContainer %}{% if var.items.isMap %} "{{ var.name }}": dict( + (_k, dict( + (_ik, {{ var.items.items.dataType }}.from_dict(_iv)) + for _ik, _iv in _v.items() + ) + if _v is not None + else None + ) + for _k, _v in obj.get("{{ var.baseName }}").items() + ) + if obj.get("{{ var.baseName }}") is not None + else None{% if not loop.last %},{% endif %} +{% endif %}{% if var.items.isArray %} "{{ var.name }}": dict( + (_k, [(_ik, {{ var.items.items.dataType }}.from_dict(_iv))] + for _ik, _iv in _v.items() + if _v is not None + else None + ) + for _k, _v in obj.get("{{ var.baseName }}").items() + ) + if obj.get("{{ var.baseName }}") is not None + else None{% if not loop.last %},{% endif %} +{% endif %}{% endif %}{% if not var.items.isContainer %} "{{ var.name }}": dict( + (_k, {{ var.items.dataType }}.from_dict(_v)) + for _k, _v in obj.get("{{ var.baseName }}").items() + ) + if obj.get("{{ var.baseName }}") is not None + else None{% if not loop.last %},{% endif %} +{% endif %}{% endif %}{% if var.items.isEnumOrRef %} "{{ var.name }}": dict((_k, _v) for _k, _v in obj.get("{{ var.baseName }}").items()){% if not loop.last %},{% endif %} +{% endif %}{% endif %}{% if var.items.isPrimitiveType %} "{{ var.name }}": obj.get("{{ var.baseName }}"){% if not loop.last %},{% endif %} +{% endif %}{% endif %}{% endif %}{% if not var.isContainer %}{% if not var.isPrimitiveType %}{% if not var.isEnumOrRef %} "{{ var.name }}": {{ var.dataType }}.from_dict(obj.get("{{ var.baseName }}")) if obj.get("{{ var.baseName }}") is not None else None{% if not loop.last %},{% endif %} +{% endif %}{% if var.isEnumOrRef %} "{{ var.name }}": obj.get("{{ var.baseName }}"){% if not loop.last %},{% endif %} +{% endif %}{% endif %}{% if var.isPrimitiveType %}{% if var.defaultValue %} "{{ var.name }}": obj.get("{{ var.baseName }}") if obj.get("{{ var.baseName }}") is not None else {{ var.defaultValue }}{% if not loop.last %},{% endif %} +{% endif %}{% if not var.defaultValue %} "{{ var.name }}": obj.get("{{ var.baseName }}"){% if not loop.last %},{% endif %} +{% endif %}{% endif %}{% endif %}{% endfor %} }) +{% if model.isAdditionalPropertiesTrue %} # store additional fields in additional_properties + for _key in obj.keys(): + if _key not in cls.__properties: + _obj.additional_properties[_key] = obj.get(_key) + +{% endif %} return _obj +{% endif %} diff --git a/generator/src/main/resources/python-nextgen-custom-client/model_oneof.mustache b/generator/src/main/resources/python-nextgen-custom-client/model_oneof.mustache deleted file mode 100644 index 15f8bdeaf..000000000 --- a/generator/src/main/resources/python-nextgen-custom-client/model_oneof.mustache +++ /dev/null @@ -1,187 +0,0 @@ -from __future__ import annotations -from inspect import getfullargspec -import json -import pprint -import re # noqa: F401 -{{#vendorExtensions.x-py-datetime-imports}}{{#-first}}from datetime import{{/-first}} {{{.}}}{{^-last}},{{/-last}}{{/vendorExtensions.x-py-datetime-imports}} -{{#vendorExtensions.x-py-typing-imports}}{{#-first}}from typing import{{/-first}} {{{.}}}{{^-last}},{{/-last}}{{/vendorExtensions.x-py-typing-imports}} -{{#vendorExtensions.x-py-pydantic-imports}}{{#-first}}from pydantic.v1 import{{/-first}} {{{.}}}{{^-last}},{{/-last}}{{/vendorExtensions.x-py-pydantic-imports}} -{{#vendorExtensions.x-py-model-imports}} -{{{.}}} -{{/vendorExtensions.x-py-model-imports}} -from typing import Any, List -from pydantic.v1 import StrictStr, Field - -{{#lambda.uppercase}}{{{classname}}}{{/lambda.uppercase}}_ONE_OF_SCHEMAS = [{{#oneOf}}"{{.}}"{{^-last}}, {{/-last}}{{/oneOf}}] - -class {{classname}}({{#parent}}{{{.}}}{{/parent}}{{^parent}}BaseModel{{/parent}}): - """ - {{{description}}}{{^description}}{{{classname}}}{{/description}} - """ -{{#composedSchemas.oneOf}} - # data type: {{{dataType}}} - {{vendorExtensions.x-py-name}}: {{{vendorExtensions.x-py-typing}}} -{{/composedSchemas.oneOf}} - actual_instance: Any - one_of_schemas: List[str] = Field({{#lambda.uppercase}}{{{classname}}}{{/lambda.uppercase}}_ONE_OF_SCHEMAS, const=True) - - class Config: - validate_assignment = True -{{#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}} - - @validator('actual_instance') - def actual_instance_must_validate_oneof(cls, v): - {{#isNullable}} - if v is None: - return v - - {{/isNullable}} - instance = cls() - error_messages = [] - match = 0 - {{#composedSchemas.oneOf}} - # validate data type: {{{dataType}}} - {{#isContainer}} - try: - instance.{{vendorExtensions.x-py-name}} = v - match += 1 - except (ValidationError, ValueError) as e: - error_messages.append(str(e)) - {{/isContainer}} - {{^isContainer}} - {{#isPrimitiveType}} - try: - instance.{{vendorExtensions.x-py-name}} = v - match += 1 - except (ValidationError, ValueError) as e: - error_messages.append(str(e)) - {{/isPrimitiveType}} - {{^isPrimitiveType}} - if not isinstance(v, {{{dataType}}}): - error_messages.append(f"Error! Input type `{type(v)}` is not `{{{dataType}}}`") - else: - match += 1 - - {{/isPrimitiveType}} - {{/isContainer}} - {{/composedSchemas.oneOf}} - if match > 1: - # more than 1 match - raise ValueError("Multiple matches found when deserializing the JSON string into {{{classname}}} with oneOf schemas: {{#oneOf}}{{{.}}}{{^-last}}, {{/-last}}{{/oneOf}}. Details: " + ", ".join(error_messages)) - elif match == 0: - # no match - raise ValueError("No match found when deserializing the JSON string into {{{classname}}} with oneOf schemas: {{#oneOf}}{{{.}}}{{^-last}}, {{/-last}}{{/oneOf}}. Details: " + ", ".join(error_messages)) - else: - return v - - @classmethod - def from_dict(cls, obj: dict) -> {{{classname}}}: - return cls.from_json(json.dumps(obj)) - - @classmethod - def from_json(cls, json_str: str) -> {{{classname}}}: - """Returns the object represented by the json string""" - instance = cls() - {{#isNullable}} - if json_str is None: - return instance - - {{/isNullable}} - error_messages = [] - match = 0 - - {{#useOneOfDiscriminatorLookup}} - {{#discriminator}} - {{#mappedModels}} - {{#-first}} - # use oneOf discriminator to lookup the data type - _data_type = json.loads(json_str).get("{{{propertyBaseName}}}") - if not _data_type: - raise ValueError("Failed to lookup data type from the field `{{{propertyBaseName}}}` in the input.") - - {{/-first}} - # check if data type is `{{{modelName}}}` - if _data_type == "{{{mappingName}}}": - instance.actual_instance = {{{modelName}}}.from_json(json_str) - return instance - - {{/mappedModels}} - {{/discriminator}} - {{/useOneOfDiscriminatorLookup}} - {{#composedSchemas.oneOf}} - {{#isContainer}} - # deserialize data into {{{dataType}}} - try: - # validation - instance.{{vendorExtensions.x-py-name}} = json.loads(json_str) - # assign value to actual_instance - instance.actual_instance = instance.{{vendorExtensions.x-py-name}} - match += 1 - except (ValidationError, ValueError) as e: - error_messages.append(str(e)) - {{/isContainer}} - {{^isContainer}} - {{#isPrimitiveType}} - # deserialize data into {{{dataType}}} - try: - # validation - instance.{{vendorExtensions.x-py-name}} = json.loads(json_str) - # assign value to actual_instance - instance.actual_instance = instance.{{vendorExtensions.x-py-name}} - match += 1 - except (ValidationError, ValueError) as e: - error_messages.append(str(e)) - {{/isPrimitiveType}} - {{^isPrimitiveType}} - # deserialize data into {{{dataType}}} - try: - instance.actual_instance = {{{dataType}}}.from_json(json_str) - match += 1 - except (ValidationError, ValueError) as e: - error_messages.append(str(e)) - {{/isPrimitiveType}} - {{/isContainer}} - {{/composedSchemas.oneOf}} - - if match > 1: - # more than 1 match - raise ValueError("Multiple matches found when deserializing the JSON string into {{{classname}}} with oneOf schemas: {{#oneOf}}{{{.}}}{{^-last}}, {{/-last}}{{/oneOf}}. Details: " + ", ".join(error_messages)) - elif match == 0: - # no match - raise ValueError("No match found when deserializing the JSON string into {{{classname}}} with oneOf schemas: {{#oneOf}}{{{.}}}{{^-last}}, {{/-last}}{{/oneOf}}. Details: " + ", ".join(error_messages)) - else: - return instance - - def to_json(self) -> str: - """Returns the JSON representation of the actual instance""" - if self.actual_instance is None: - return "null" - - to_json = getattr(self.actual_instance, "to_json", None) - if callable(to_json): - return self.actual_instance.to_json() - else: - return json.dumps(self.actual_instance) - - def to_dict(self) -> dict: - """Returns the dict representation of the actual instance""" - if self.actual_instance is None: - return None - - to_dict = getattr(self.actual_instance, "to_dict", None) - if callable(to_dict): - return self.actual_instance.to_dict() - else: - # primitive type - return self.actual_instance - - def to_str(self) -> str: - """Returns the string representation of the actual instance""" - return pprint.pformat(self.dict()) diff --git a/generator/src/main/resources/python-nextgen-custom-client/model_oneof.pebble b/generator/src/main/resources/python-nextgen-custom-client/model_oneof.pebble new file mode 100644 index 000000000..6ebfbc7d1 --- /dev/null +++ b/generator/src/main/resources/python-nextgen-custom-client/model_oneof.pebble @@ -0,0 +1,187 @@ +from __future__ import annotations +from inspect import getfullargspec +import json +import pprint +import re # noqa: F401 +{% if model.vendorExtensions['x-py-datetime-imports'] is not empty %}from datetime import {% for imp in model.vendorExtensions['x-py-datetime-imports'] %}{{ imp }}{% if not loop.last %},{% endif %}{% endfor %}{% endif %} +{% if model.vendorExtensions['x-py-typing-imports'] is not empty %}from typing import {% for imp in model.vendorExtensions['x-py-typing-imports'] %}{{ imp }}{% if not loop.last %},{% endif %}{% endfor %}{% endif %} +{% if model.vendorExtensions['x-py-pydantic-imports'] is not empty %}from pydantic.v1 import {% for imp in model.vendorExtensions['x-py-pydantic-imports'] %}{{ imp }}{% if not loop.last %},{% endif %}{% endfor %}{% endif %} +{% for imp in model.vendorExtensions['x-py-model-imports'] %} +{{ imp }} +{% endfor %} +from typing import Any, List +from pydantic.v1 import StrictStr, Field + +{{ model.classname | upper }}_ONE_OF_SCHEMAS = [{% for one in model.oneOf %}"{{ one }}"{% if not loop.last %}, {% endif %}{% endfor %}] + +class {{ model.classname }}({% if model.parent %}{{ model.parent }}{% else %}BaseModel{% endif %}): + """ + {% if model.description %}{{ model.description }}{% else %}{{ model.classname }}{% endif %} + """ +{% for one in model.composedSchemas.oneOf %} + # data type: {{ one.dataType }} + {{ one.vendorExtensions['x-py-name'] }}: {{ one.vendorExtensions['x-py-typing'] }} +{% endfor %} + actual_instance: Any + one_of_schemas: List[str] = Field({{ model.classname | upper }}_ONE_OF_SCHEMAS, const=True) + + class Config: + validate_assignment = True +{% if model.discriminator is not null %} + + discriminator_value_class_map = { +{% for child in model.discriminator.children %} + '{% if not child.vendorExtensions['x-discriminator-value'] %}{{ child.name }}{% else %}{{ child.vendorExtensions['x-discriminator-value'] }}{% endif %}': '{{ child.classname }}'{% if not loop.last %},{% endif %} +{% endfor %} + } +{% endif %} + + @validator('actual_instance') + def actual_instance_must_validate_oneof(cls, v): + {% if model.isNullable %} + if v is None: + return v + + {% endif %} + instance = cls() + error_messages = [] + match = 0 + {% for one in model.composedSchemas.oneOf %} + # validate data type: {{ one.dataType }} + {% if one.isContainer %} + try: + instance.{{ one.vendorExtensions['x-py-name'] }} = v + match += 1 + except (ValidationError, ValueError) as e: + error_messages.append(str(e)) + {% endif %} + {% if not one.isContainer %} + {% if one.isPrimitiveType %} + try: + instance.{{ one.vendorExtensions['x-py-name'] }} = v + match += 1 + except (ValidationError, ValueError) as e: + error_messages.append(str(e)) + {% endif %} + {% if not one.isPrimitiveType %} + if not isinstance(v, {{ one.dataType }}): + error_messages.append(f"Error! Input type `{type(v)}` is not `{{ one.dataType }}`") + else: + match += 1 + + {% endif %} + {% endif %} + {% endfor %} + if match > 1: + # more than 1 match + raise ValueError("Multiple matches found when deserializing the JSON string into {{ model.classname }} with oneOf schemas: {% for one in model.oneOf %}{{ one }}{% if not loop.last %}, {% endif %}{% endfor %}. Details: " + ", ".join(error_messages)) + elif match == 0: + # no match + raise ValueError("No match found when deserializing the JSON string into {{ model.classname }} with oneOf schemas: {% for one in model.oneOf %}{{ one }}{% if not loop.last %}, {% endif %}{% endfor %}. Details: " + ", ".join(error_messages)) + else: + return v + + @classmethod + def from_dict(cls, obj: dict) -> {{ model.classname }}: + return cls.from_json(json.dumps(obj)) + + @classmethod + def from_json(cls, json_str: str) -> {{ model.classname }}: + """Returns the object represented by the json string""" + instance = cls() + {% if model.isNullable %} + if json_str is None: + return instance + + {% endif %} + error_messages = [] + match = 0 + + {% if useOneOfDiscriminatorLookup %} + {% if model.discriminator is not null %} + {% for mapped in model.discriminator.mappedModels %} + {% if loop.first %} + # use oneOf discriminator to lookup the data type + _data_type = json.loads(json_str).get("{{ model.discriminator.propertyBaseName }}") + if not _data_type: + raise ValueError("Failed to lookup data type from the field `{{ model.discriminator.propertyBaseName }}` in the input.") + + {% endif %} + # check if data type is `{{ mapped.modelName }}` + if _data_type == "{{ mapped.mappingName }}": + instance.actual_instance = {{ mapped.modelName }}.from_json(json_str) + return instance + + {% endfor %} + {% endif %} + {% endif %} + {% for one in model.composedSchemas.oneOf %} + {% if one.isContainer %} + # deserialize data into {{ one.dataType }} + try: + # validation + instance.{{ one.vendorExtensions['x-py-name'] }} = json.loads(json_str) + # assign value to actual_instance + instance.actual_instance = instance.{{ one.vendorExtensions['x-py-name'] }} + match += 1 + except (ValidationError, ValueError) as e: + error_messages.append(str(e)) + {% endif %} + {% if not one.isContainer %} + {% if one.isPrimitiveType %} + # deserialize data into {{ one.dataType }} + try: + # validation + instance.{{ one.vendorExtensions['x-py-name'] }} = json.loads(json_str) + # assign value to actual_instance + instance.actual_instance = instance.{{ one.vendorExtensions['x-py-name'] }} + match += 1 + except (ValidationError, ValueError) as e: + error_messages.append(str(e)) + {% endif %} + {% if not one.isPrimitiveType %} + # deserialize data into {{ one.dataType }} + try: + instance.actual_instance = {{ one.dataType }}.from_json(json_str) + match += 1 + except (ValidationError, ValueError) as e: + error_messages.append(str(e)) + {% endif %} + {% endif %} + {% endfor %} + + if match > 1: + # more than 1 match + raise ValueError("Multiple matches found when deserializing the JSON string into {{ model.classname }} with oneOf schemas: {% for one in model.oneOf %}{{ one }}{% if not loop.last %}, {% endif %}{% endfor %}. Details: " + ", ".join(error_messages)) + elif match == 0: + # no match + raise ValueError("No match found when deserializing the JSON string into {{ model.classname }} with oneOf schemas: {% for one in model.oneOf %}{{ one }}{% if not loop.last %}, {% endif %}{% endfor %}. Details: " + ", ".join(error_messages)) + else: + return instance + + def to_json(self) -> str: + """Returns the JSON representation of the actual instance""" + if self.actual_instance is None: + return "null" + + to_json = getattr(self.actual_instance, "to_json", None) + if callable(to_json): + return self.actual_instance.to_json() + else: + return json.dumps(self.actual_instance) + + def to_dict(self) -> dict: + """Returns the dict representation of the actual instance""" + if self.actual_instance is None: + return None + + to_dict = getattr(self.actual_instance, "to_dict", None) + if callable(to_dict): + return self.actual_instance.to_dict() + else: + # primitive type + return self.actual_instance + + def to_str(self) -> str: + """Returns the string representation of the actual instance""" + return pprint.pformat(self.dict()) diff --git a/generator/src/main/resources/python-nextgen-custom-client/partial_header.mustache b/generator/src/main/resources/python-nextgen-custom-client/partial_header.mustache deleted file mode 100644 index d952d4235..000000000 --- a/generator/src/main/resources/python-nextgen-custom-client/partial_header.mustache +++ /dev/null @@ -1,19 +0,0 @@ -""" -{{#appName}} - {{{.}}} - -{{/appName}} -{{#appDescription}} - {{{.}}} # noqa: E501 - -{{/appDescription}} - {{#version}} - The version of the OpenAPI document: {{{.}}} - {{/version}} - {{#infoEmail}} - Contact: {{{.}}} - {{/infoEmail}} - Generated by OpenAPI Generator (https://openapi-generator.tech) - - Do not edit the class manually. -""" diff --git a/generator/src/main/resources/python-nextgen-custom-client/partial_header.pebble b/generator/src/main/resources/python-nextgen-custom-client/partial_header.pebble new file mode 100644 index 000000000..5a4af8907 --- /dev/null +++ b/generator/src/main/resources/python-nextgen-custom-client/partial_header.pebble @@ -0,0 +1,11 @@ +""" +{% if appName %} {{ appName }} + +{% endif %}{% if appDescription %} {{ appDescription }} # noqa: E501 + +{% endif %} {% if version %}The version of the OpenAPI document: {{ version }} + {% endif %}{% if infoEmail %}Contact: {{ infoEmail }} + {% endif %}Generated by OpenAPI Generator (https://openapi-generator.tech) + + Do not edit the class manually. +""" diff --git a/generator/src/main/resources/python-nextgen-custom-client/pyproject.mustache b/generator/src/main/resources/python-nextgen-custom-client/pyproject.mustache deleted file mode 100644 index 726b08b9c..000000000 --- a/generator/src/main/resources/python-nextgen-custom-client/pyproject.mustache +++ /dev/null @@ -1,39 +0,0 @@ -[tool.poetry] -name = "{{{packageName}}}" -version = "{{{packageVersion}}}" -description = "{{{appName}}}" -authors = ["{{infoName}}{{^infoName}}OpenAPI Generator Community{{/infoName}} <{{infoEmail}}{{^infoEmail}}team@openapitools.org{{/infoEmail}}>"] -license = "{{{licenseInfo}}}{{^licenseInfo}}NoLicense{{/licenseInfo}}" -readme = "README.md" -repository = "https://github.com/{{{gitRepoId}}}/{{{gitUserId}}}" -keywords = ["OpenAPI", "OpenAPI-Generator", "{{{appName}}}"] - -[tool.poetry.dependencies] -python = "^3.10" - -urllib3 = ">= 1.25.3" -python-dateutil = ">=2.8.2" -{{#asyncio}} -aiohttp = ">= 3.8.4" -{{/asyncio}} -{{#tornado}} -tornado = ">=4.2,<5" -{{/tornado}} -{{#hasHttpSignatureMethods}} -pem = ">= 19.3.0" -pycryptodome = ">= 3.10.0" -{{/hasHttpSignatureMethods}} -pydantic = "^1.10.5, <2" -aenum = ">=3.1.11" - -[tool.poetry.dev-dependencies] -pytest = ">=7.2.1" -tox = ">=3.10.0" -flake8 = ">=4.0.0" - -[build-system] -requires = ["setuptools"] -build-backend = "setuptools.build_meta" - -[tool.pylint.'MESSAGES CONTROL'] -extension-pkg-whitelist = "pydantic" diff --git a/generator/src/main/resources/python-nextgen-custom-client/python_doc_auth_partial.mustache b/generator/src/main/resources/python-nextgen-custom-client/python_doc_auth_partial.mustache deleted file mode 100644 index f478fe0f1..000000000 --- a/generator/src/main/resources/python-nextgen-custom-client/python_doc_auth_partial.mustache +++ /dev/null @@ -1,108 +0,0 @@ -# Defining the host is optional and defaults to {{{basePath}}} -# See configuration.py for a list of all supported configuration parameters. -configuration = {{{packageName}}}.Configuration( - host = "{{{basePath}}}" -) - -{{#hasAuthMethods}} -# The client must configure the authentication and authorization parameters -# in accordance with the API server security policy. -# Examples for each auth method are provided below, use the example that -# satisfies your auth use case. -{{#authMethods}} -{{#isBasic}} -{{#isBasicBasic}} - -# Configure HTTP basic authorization: {{{name}}} -configuration = {{{packageName}}}.Configuration( - username = os.environ["USERNAME"], - password = os.environ["PASSWORD"] -) -{{/isBasicBasic}} -{{#isBasicBearer}} - -# Configure Bearer authorization{{#bearerFormat}} ({{{.}}}){{/bearerFormat}}: {{{name}}} -configuration = {{{packageName}}}.Configuration( - access_token = os.environ["BEARER_TOKEN"] -) -{{/isBasicBearer}} -{{#isHttpSignature}} - -# Configure HTTP message signature: {{{name}}} -# The HTTP Signature Header mechanism that can be used by a client to -# authenticate the sender of a message and ensure that particular headers -# have not been modified in transit. -# -# You can specify the signing key-id, private key path, signing scheme, -# signing algorithm, list of signed headers and signature max validity. -# The 'key_id' parameter is an opaque string that the API server can use -# to lookup the client and validate the signature. -# The 'private_key_path' parameter should be the path to a file that -# contains a DER or base-64 encoded private key. -# The 'private_key_passphrase' parameter is optional. Set the passphrase -# if the private key is encrypted. -# The 'signed_headers' parameter is used to specify the list of -# HTTP headers included when generating the signature for the message. -# You can specify HTTP headers that you want to protect with a cryptographic -# signature. Note that proxies may add, modify or remove HTTP headers -# for legitimate reasons, so you should only add headers that you know -# will not be modified. For example, if you want to protect the HTTP request -# body, you can specify the Digest header. In that case, the client calculates -# the digest of the HTTP request body and includes the digest in the message -# signature. -# The 'signature_max_validity' parameter is optional. It is configured as a -# duration to express when the signature ceases to be valid. The client calculates -# the expiration date every time it generates the cryptographic signature -# of an HTTP request. The API server may have its own security policy -# that controls the maximum validity of the signature. The client max validity -# must be lower than the server max validity. -# The time on the client and server must be synchronized, otherwise the -# server may reject the client signature. -# -# The client must use a combination of private key, signing scheme, -# signing algorithm and hash algorithm that matches the security policy of -# the API server. -# -# See {{{packageName}}}.signing for a list of all supported parameters. -from {{{packageName}}} import signing -import datetime - -configuration = {{{packageName}}}.Configuration( - host = "{{{basePath}}}", - signing_info = {{{packageName}}}.HttpSigningConfiguration( - key_id = 'my-key-id', - private_key_path = 'private_key.pem', - private_key_passphrase = 'YOUR_PASSPHRASE', - signing_scheme = {{{packageName}}}.signing.SCHEME_HS2019, - signing_algorithm = {{{packageName}}}.signing.ALGORITHM_ECDSA_MODE_FIPS_186_3, - hash_algorithm = {{{packageName}}}.signing.SCHEME_RSA_SHA256, - signed_headers = [ - {{{packageName}}}.signing.HEADER_REQUEST_TARGET, - {{{packageName}}}.signing.HEADER_CREATED, - {{{packageName}}}.signing.HEADER_EXPIRES, - {{{packageName}}}.signing.HEADER_HOST, - {{{packageName}}}.signing.HEADER_DATE, - {{{packageName}}}.signing.HEADER_DIGEST, - 'Content-Type', - 'Content-Length', - 'User-Agent' - ], - signature_max_validity = datetime.timedelta(minutes=5) - ) -) -{{/isHttpSignature}} -{{/isBasic}} -{{#isApiKey}} - -# Configure API key authorization: {{{name}}} -configuration.api_key['{{{name}}}'] = os.environ["API_KEY"] - -# Uncomment below to setup prefix (e.g. Bearer) for API key, if needed -# configuration.api_key_prefix['{{name}}'] = 'Bearer' -{{/isApiKey}} -{{#isOAuth}} - -configuration.access_token = os.environ["ACCESS_TOKEN"] -{{/isOAuth}} -{{/authMethods}} -{{/hasAuthMethods}} diff --git a/generator/src/main/resources/python-nextgen-custom-client/python_doc_auth_partial.pebble b/generator/src/main/resources/python-nextgen-custom-client/python_doc_auth_partial.pebble new file mode 100644 index 000000000..600379dfa --- /dev/null +++ b/generator/src/main/resources/python-nextgen-custom-client/python_doc_auth_partial.pebble @@ -0,0 +1,29 @@ +# Defining the host is optional and defaults to {{ basePath }} +# See configuration.py for a list of all supported configuration parameters. +configuration = {{ packageName }}.Configuration( + host = "{{ basePath }}" +) + +{% if operation.hasAuthMethods %}# The client must configure the authentication and authorization parameters +# in accordance with the API server security policy. +# Examples for each auth method are provided below, use the example that +# satisfies your auth use case. +{% for authMethod in operation.authMethods %}{% if authMethod.isBasic %}{% if authMethod.isBasicBasic %} +# Configure HTTP basic authorization: {{ authMethod.name }} +configuration = {{ packageName }}.Configuration( + username = os.environ["USERNAME"], + password = os.environ["PASSWORD"] +) +{% endif %}{% if authMethod.isBasicBearer %} +# Configure Bearer authorization{% if authMethod.bearerFormat %} ({{ authMethod.bearerFormat }}){% endif %}: {{ authMethod.name }} +configuration = {{ packageName }}.Configuration( + access_token = os.environ["BEARER_TOKEN"] +){% endif %}{% endif %}{% if authMethod.isApiKey %} +# Configure API key authorization: {{ authMethod.name }} +configuration.api_key['{{ authMethod.name }}'] = os.environ["API_KEY"] + +# Uncomment below to setup prefix (e.g. Bearer) for API key, if needed +# configuration.api_key_prefix['{{ authMethod.name }}'] = 'Bearer' +{% endif %}{% if authMethod.isOAuth %} +configuration.access_token = os.environ["ACCESS_TOKEN"] +{% endif %}{% endfor %}{% endif %} diff --git a/generator/src/main/resources/python-nextgen-custom-client/requirements.mustache b/generator/src/main/resources/python-nextgen-custom-client/requirements.mustache deleted file mode 100644 index e18a97e55..000000000 --- a/generator/src/main/resources/python-nextgen-custom-client/requirements.mustache +++ /dev/null @@ -1,8 +0,0 @@ -python_dateutil >= 2.5.3 -setuptools >= 21.0.0 -urllib3 >= 1.25.3 -pydantic >= 1.10.5, < 2 -aenum >= 3.1.11 -{{#asyncio}} -aiohttp >= 3.0.0 -{{/asyncio}} diff --git a/generator/src/main/resources/python-nextgen-custom-client/rest.mustache b/generator/src/main/resources/python-nextgen-custom-client/rest.pebble similarity index 98% rename from generator/src/main/resources/python-nextgen-custom-client/rest.mustache rename to generator/src/main/resources/python-nextgen-custom-client/rest.pebble index 89875d655..f40781359 100644 --- a/generator/src/main/resources/python-nextgen-custom-client/rest.mustache +++ b/generator/src/main/resources/python-nextgen-custom-client/rest.pebble @@ -1,6 +1,6 @@ # coding: utf-8 -{{>partial_header}} +{% include "partial_header.pebble" %} import io import json @@ -11,7 +11,7 @@ import ssl from urllib.parse import urlencode, quote_plus import urllib3 -from {{packageName}}.exceptions import ApiException, UnauthorizedException, ForbiddenException, NotFoundException, ServiceException, ApiValueError +from {{ packageName }}.exceptions import ApiException, UnauthorizedException, ForbiddenException, NotFoundException, ServiceException, ApiValueError logger = logging.getLogger(__name__) diff --git a/generator/src/main/resources/python-nextgen-custom-client/setup.mustache b/generator/src/main/resources/python-nextgen-custom-client/setup.mustache deleted file mode 100644 index 254f442ca..000000000 --- a/generator/src/main/resources/python-nextgen-custom-client/setup.mustache +++ /dev/null @@ -1,55 +0,0 @@ -# coding: utf-8 - -{{>partial_header}} - -from setuptools import setup, find_packages # noqa: H301 - -# To install the library, run the following -# -# python setup.py install -# -# prerequisite: setuptools -# http://pypi.python.org/pypi/setuptools -NAME = "{{{projectName}}}" -VERSION = "{{packageVersion}}" -PYTHON_REQUIRES = ">=3.10" -{{#apiInfo}} -{{#apis}} -{{#-last}} -REQUIRES = [ - "urllib3 >= 1.25.3", - "python-dateutil", -{{#asyncio}} - "aiohttp >= 3.0.0", -{{/asyncio}} -{{#tornado}} - "tornado>=4.2,<5", -{{/tornado}} -{{#hasHttpSignatureMethods}} - "pem>=19.3.0", - "pycryptodome>=3.10.0", -{{/hasHttpSignatureMethods}} - "pydantic >= 1.10.5, < 2", - "aenum" -] - -setup( - name=NAME, - version=VERSION, - description="{{appName}}", - author="{{infoName}}{{^infoName}}OpenAPI Generator community{{/infoName}}", - author_email="{{infoEmail}}{{^infoEmail}}team@openapitools.org{{/infoEmail}}", - url="{{packageUrl}}", - keywords=["OpenAPI", "OpenAPI-Generator", "{{{appName}}}"], - install_requires=REQUIRES, - packages=find_packages(exclude=["test", "tests"]), - include_package_data=True, - {{#licenseInfo}}license="{{.}}", - {{/licenseInfo}}long_description_content_type='text/markdown', - long_description="""\ - {{appDescription}} # noqa: E501 - """ -) -{{/-last}} -{{/apis}} -{{/apiInfo}} diff --git a/generator/src/main/resources/python-nextgen-custom-client/setup_cfg.mustache b/generator/src/main/resources/python-nextgen-custom-client/setup_cfg.mustache deleted file mode 100644 index 11433ee87..000000000 --- a/generator/src/main/resources/python-nextgen-custom-client/setup_cfg.mustache +++ /dev/null @@ -1,2 +0,0 @@ -[flake8] -max-line-length=99 diff --git a/generator/src/main/resources/python-nextgen-custom-client/signing.mustache b/generator/src/main/resources/python-nextgen-custom-client/signing.mustache deleted file mode 100644 index 8dca7e2d1..000000000 --- a/generator/src/main/resources/python-nextgen-custom-client/signing.mustache +++ /dev/null @@ -1,403 +0,0 @@ -{{>partial_header}} - -from base64 import b64encode -from Crypto.IO import PEM, PKCS8 -from Crypto.Hash import SHA256, SHA512 -from Crypto.PublicKey import RSA, ECC -from Crypto.Signature import PKCS1_v1_5, pss, DSS -from email.utils import formatdate -import json -import os -import re -from time import time -from urllib.parse import urlencode, urlparse - -# The constants below define a subset of HTTP headers that can be included in the -# HTTP signature scheme. Additional headers may be included in the signature. - -# The '(request-target)' header is a calculated field that includes the HTTP verb, -# the URL path and the URL query. -HEADER_REQUEST_TARGET = '(request-target)' -# The time when the HTTP signature was generated. -HEADER_CREATED = '(created)' -# The time when the HTTP signature expires. The API server should reject HTTP requests -# that have expired. -HEADER_EXPIRES = '(expires)' -# The 'Host' header. -HEADER_HOST = 'Host' -# The 'Date' header. -HEADER_DATE = 'Date' -# When the 'Digest' header is included in the HTTP signature, the client automatically -# computes the digest of the HTTP request body, per RFC 3230. -HEADER_DIGEST = 'Digest' -# The 'Authorization' header is automatically generated by the client. It includes -# the list of signed headers and a base64-encoded signature. -HEADER_AUTHORIZATION = 'Authorization' - -# The constants below define the cryptographic schemes for the HTTP signature scheme. -SCHEME_HS2019 = 'hs2019' -SCHEME_RSA_SHA256 = 'rsa-sha256' -SCHEME_RSA_SHA512 = 'rsa-sha512' - -# The constants below define the signature algorithms that can be used for the HTTP -# signature scheme. -ALGORITHM_RSASSA_PSS = 'RSASSA-PSS' -ALGORITHM_RSASSA_PKCS1v15 = 'RSASSA-PKCS1-v1_5' - -ALGORITHM_ECDSA_MODE_FIPS_186_3 = 'fips-186-3' -ALGORITHM_ECDSA_MODE_DETERMINISTIC_RFC6979 = 'deterministic-rfc6979' -ALGORITHM_ECDSA_KEY_SIGNING_ALGORITHMS = { - ALGORITHM_ECDSA_MODE_FIPS_186_3, - ALGORITHM_ECDSA_MODE_DETERMINISTIC_RFC6979 -} - -# The cryptographic hash algorithm for the message signature. -HASH_SHA256 = 'sha256' -HASH_SHA512 = 'sha512' - - -class HttpSigningConfiguration(object): - """The configuration parameters for the HTTP signature security scheme. - The HTTP signature security scheme is used to sign HTTP requests with a private key - which is in possession of the API client. - An 'Authorization' header is calculated by creating a hash of select headers, - and optionally the body of the HTTP request, then signing the hash value using - a private key. The 'Authorization' header is added to outbound HTTP requests. - - :param key_id: A string value specifying the identifier of the cryptographic key, - when signing HTTP requests. - :param signing_scheme: A string value specifying the signature scheme, when - signing HTTP requests. - Supported value are hs2019, rsa-sha256, rsa-sha512. - Avoid using rsa-sha256, rsa-sha512 as they are deprecated. These values are - available for server-side applications that only support the older - HTTP signature algorithms. - :param private_key_path: A string value specifying the path of the file containing - a private key. The private key is used to sign HTTP requests. - :param private_key_passphrase: A string value specifying the passphrase to decrypt - the private key. - :param signed_headers: A list of strings. Each value is the name of a HTTP header - that must be included in the HTTP signature calculation. - The two special signature headers '(request-target)' and '(created)' SHOULD be - included in SignedHeaders. - The '(created)' header expresses when the signature was created. - The '(request-target)' header is a concatenation of the lowercased :method, an - ASCII space, and the :path pseudo-headers. - When signed_headers is not specified, the client defaults to a single value, - '(created)', in the list of HTTP headers. - When SignedHeaders contains the 'Digest' value, the client performs the - following operations: - 1. Calculate a digest of request body, as specified in RFC3230, section 4.3.2. - 2. Set the 'Digest' header in the request body. - 3. Include the 'Digest' header and value in the HTTP signature. - :param signing_algorithm: A string value specifying the signature algorithm, when - signing HTTP requests. - Supported values are: - 1. For RSA keys: RSASSA-PSS, RSASSA-PKCS1-v1_5. - 2. For ECDSA keys: fips-186-3, deterministic-rfc6979. - If None, the signing algorithm is inferred from the private key. - The default signing algorithm for RSA keys is RSASSA-PSS. - The default signing algorithm for ECDSA keys is fips-186-3. - :param hash_algorithm: The hash algorithm for the signature. Supported values are - sha256 and sha512. - If the signing_scheme is rsa-sha256, the hash algorithm must be set - to None or sha256. - If the signing_scheme is rsa-sha512, the hash algorithm must be set - to None or sha512. - :param signature_max_validity: The signature max validity, expressed as - a datetime.timedelta value. It must be a positive value. - """ - def __init__(self, key_id, signing_scheme, private_key_path, - private_key_passphrase=None, - signed_headers=None, - signing_algorithm=None, - hash_algorithm=None, - signature_max_validity=None): - self.key_id = key_id - if signing_scheme not in {SCHEME_HS2019, SCHEME_RSA_SHA256, SCHEME_RSA_SHA512}: - raise Exception("Unsupported security scheme: {0}".format(signing_scheme)) - self.signing_scheme = signing_scheme - if not os.path.exists(private_key_path): - raise Exception("Private key file does not exist") - self.private_key_path = private_key_path - self.private_key_passphrase = private_key_passphrase - self.signing_algorithm = signing_algorithm - self.hash_algorithm = hash_algorithm - if signing_scheme == SCHEME_RSA_SHA256: - if self.hash_algorithm is None: - self.hash_algorithm = HASH_SHA256 - elif self.hash_algorithm != HASH_SHA256: - raise Exception("Hash algorithm must be sha256 when security scheme is %s" % - SCHEME_RSA_SHA256) - elif signing_scheme == SCHEME_RSA_SHA512: - if self.hash_algorithm is None: - self.hash_algorithm = HASH_SHA512 - elif self.hash_algorithm != HASH_SHA512: - raise Exception("Hash algorithm must be sha512 when security scheme is %s" % - SCHEME_RSA_SHA512) - elif signing_scheme == SCHEME_HS2019: - if self.hash_algorithm is None: - self.hash_algorithm = HASH_SHA256 - elif self.hash_algorithm not in {HASH_SHA256, HASH_SHA512}: - raise Exception("Invalid hash algorithm") - if signature_max_validity is not None and signature_max_validity.total_seconds() < 0: - raise Exception("The signature max validity must be a positive value") - self.signature_max_validity = signature_max_validity - # If the user has not provided any signed_headers, the default must be set to '(created)', - # as specified in the 'HTTP signature' standard. - if signed_headers is None or len(signed_headers) == 0: - signed_headers = [HEADER_CREATED] - if self.signature_max_validity is None and HEADER_EXPIRES in signed_headers: - raise Exception( - "Signature max validity must be set when " - "'(expires)' signature parameter is specified") - if len(signed_headers) != len(set(signed_headers)): - raise Exception("Cannot have duplicates in the signed_headers parameter") - if HEADER_AUTHORIZATION in signed_headers: - raise Exception("'Authorization' header cannot be included in signed headers") - self.signed_headers = signed_headers - self.private_key = None - """The private key used to sign HTTP requests. - Initialized when the PEM-encoded private key is loaded from a file. - """ - self.host = None - """The host name, optionally followed by a colon and TCP port number. - """ - self._load_private_key() - - def get_http_signature_headers(self, resource_path, method, headers, body, query_params): - """Create a cryptographic message signature for the HTTP request and add the signed headers. - - :param resource_path : A string representation of the HTTP request resource path. - :param method: A string representation of the HTTP request method, e.g. GET, POST. - :param headers: A dict containing the HTTP request headers. - :param body: The object representing the HTTP request body. - :param query_params: A string representing the HTTP request query parameters. - :return: A dict of HTTP headers that must be added to the outbound HTTP request. - """ - if method is None: - raise Exception("HTTP method must be set") - if resource_path is None: - raise Exception("Resource path must be set") - - signed_headers_list, request_headers_dict = self._get_signed_header_info( - resource_path, method, headers, body, query_params) - - header_items = [ - "{0}: {1}".format(key.lower(), value) for key, value in signed_headers_list] - string_to_sign = "\n".join(header_items) - - digest, digest_prefix = self._get_message_digest(string_to_sign.encode()) - b64_signed_msg = self._sign_digest(digest) - - request_headers_dict[HEADER_AUTHORIZATION] = self._get_authorization_header( - signed_headers_list, b64_signed_msg) - - return request_headers_dict - - def get_public_key(self): - """Returns the public key object associated with the private key. - """ - pubkey = None - if isinstance(self.private_key, RSA.RsaKey): - pubkey = self.private_key.publickey() - elif isinstance(self.private_key, ECC.EccKey): - pubkey = self.private_key.public_key() - return pubkey - - def _load_private_key(self): - """Load the private key used to sign HTTP requests. - The private key is used to sign HTTP requests as defined in - https://datatracker.ietf.org/doc/draft-cavage-http-signatures/. - """ - if self.private_key is not None: - return - with open(self.private_key_path, 'r') as f: - pem_data = f.read() - # Verify PEM Pre-Encapsulation Boundary - r = re.compile(r"\s*-----BEGIN (.*)-----\s+") - m = r.match(pem_data) - if not m: - raise ValueError("Not a valid PEM pre boundary") - pem_header = m.group(1) - if pem_header == 'RSA PRIVATE KEY': - self.private_key = RSA.importKey(pem_data, self.private_key_passphrase) - elif pem_header == 'EC PRIVATE KEY': - self.private_key = ECC.import_key(pem_data, self.private_key_passphrase) - elif pem_header in {'PRIVATE KEY', 'ENCRYPTED PRIVATE KEY'}: - # Key is in PKCS8 format, which is capable of holding many different - # types of private keys, not just EC keys. - (key_binary, pem_header, is_encrypted) = \ - PEM.decode(pem_data, self.private_key_passphrase) - (oid, privkey, params) = \ - PKCS8.unwrap(key_binary, passphrase=self.private_key_passphrase) - if oid == '1.2.840.10045.2.1': - self.private_key = ECC.import_key(pem_data, self.private_key_passphrase) - else: - raise Exception("Unsupported key: {0}. OID: {1}".format(pem_header, oid)) - else: - raise Exception("Unsupported key: {0}".format(pem_header)) - # Validate the specified signature algorithm is compatible with the private key. - if self.signing_algorithm is not None: - supported_algs = None - if isinstance(self.private_key, RSA.RsaKey): - supported_algs = {ALGORITHM_RSASSA_PSS, ALGORITHM_RSASSA_PKCS1v15} - elif isinstance(self.private_key, ECC.EccKey): - supported_algs = ALGORITHM_ECDSA_KEY_SIGNING_ALGORITHMS - if supported_algs is not None and self.signing_algorithm not in supported_algs: - raise Exception( - "Signing algorithm {0} is not compatible with private key".format( - self.signing_algorithm)) - - def _get_signed_header_info(self, resource_path, method, headers, body, query_params): - """Build the HTTP headers (name, value) that need to be included in - the HTTP signature scheme. - - :param resource_path : A string representation of the HTTP request resource path. - :param method: A string representation of the HTTP request method, e.g. GET, POST. - :param headers: A dict containing the HTTP request headers. - :param body: The object (e.g. a dict) representing the HTTP request body. - :param query_params: A string representing the HTTP request query parameters. - :return: A tuple containing two dict objects: - The first dict contains the HTTP headers that are used to calculate - the HTTP signature. - The second dict contains the HTTP headers that must be added to - the outbound HTTP request. - """ - - if body is None: - body = '' - else: - body = body.to_json() - - # Build the '(request-target)' HTTP signature parameter. - target_host = urlparse(self.host).netloc - target_path = urlparse(self.host).path - request_target = method.lower() + " " + target_path + resource_path - if query_params: - request_target += "?" + urlencode(query_params) - - # Get UNIX time, e.g. seconds since epoch, not including leap seconds. - now = time() - # Format date per RFC 7231 section-7.1.1.2. An example is: - # Date: Wed, 21 Oct 2015 07:28:00 GMT - cdate = formatdate(timeval=now, localtime=False, usegmt=True) - # The '(created)' value MUST be a Unix timestamp integer value. - # Subsecond precision is not supported. - created = int(now) - if self.signature_max_validity is not None: - expires = now + self.signature_max_validity.total_seconds() - - signed_headers_list = [] - request_headers_dict = {} - for hdr_key in self.signed_headers: - hdr_key = hdr_key.lower() - if hdr_key == HEADER_REQUEST_TARGET: - value = request_target - elif hdr_key == HEADER_CREATED: - value = '{0}'.format(created) - elif hdr_key == HEADER_EXPIRES: - value = '{0}'.format(expires) - elif hdr_key == HEADER_DATE.lower(): - value = cdate - request_headers_dict[HEADER_DATE] = '{0}'.format(cdate) - elif hdr_key == HEADER_DIGEST.lower(): - request_body = body.encode() - body_digest, digest_prefix = self._get_message_digest(request_body) - b64_body_digest = b64encode(body_digest.digest()) - value = digest_prefix + b64_body_digest.decode('ascii') - request_headers_dict[HEADER_DIGEST] = '{0}{1}'.format( - digest_prefix, b64_body_digest.decode('ascii')) - elif hdr_key == HEADER_HOST.lower(): - value = target_host - request_headers_dict[HEADER_HOST] = '{0}'.format(target_host) - else: - value = next((v for k, v in headers.items() if k.lower() == hdr_key), None) - if value is None: - raise Exception( - "Cannot sign HTTP request. " - "Request does not contain the '{0}' header".format(hdr_key)) - signed_headers_list.append((hdr_key, value)) - - return signed_headers_list, request_headers_dict - - def _get_message_digest(self, data): - """Calculates and returns a cryptographic digest of a specified HTTP request. - - :param data: The string representation of the date to be hashed with a cryptographic hash. - :return: A tuple of (digest, prefix). - The digest is a hashing object that contains the cryptographic digest of - the HTTP request. - The prefix is a string that identifies the cryptographic hash. It is used - to generate the 'Digest' header as specified in RFC 3230. - """ - if self.hash_algorithm == HASH_SHA512: - digest = SHA512.new() - prefix = 'SHA-512=' - elif self.hash_algorithm == HASH_SHA256: - digest = SHA256.new() - prefix = 'SHA-256=' - else: - raise Exception("Unsupported hash algorithm: {0}".format(self.hash_algorithm)) - digest.update(data) - return digest, prefix - - def _sign_digest(self, digest): - """Signs a message digest with a private key specified in the signing_info. - - :param digest: A hashing object that contains the cryptographic digest of the HTTP request. - :return: A base-64 string representing the cryptographic signature of the input digest. - """ - sig_alg = self.signing_algorithm - if isinstance(self.private_key, RSA.RsaKey): - if sig_alg is None or sig_alg == ALGORITHM_RSASSA_PSS: - # RSASSA-PSS in Section 8.1 of RFC8017. - signature = pss.new(self.private_key).sign(digest) - elif sig_alg == ALGORITHM_RSASSA_PKCS1v15: - # RSASSA-PKCS1-v1_5 in Section 8.2 of RFC8017. - signature = PKCS1_v1_5.new(self.private_key).sign(digest) - else: - raise Exception("Unsupported signature algorithm: {0}".format(sig_alg)) - elif isinstance(self.private_key, ECC.EccKey): - if sig_alg is None: - sig_alg = ALGORITHM_ECDSA_MODE_FIPS_186_3 - if sig_alg in ALGORITHM_ECDSA_KEY_SIGNING_ALGORITHMS: - # draft-ietf-httpbis-message-signatures-00 does not specify the ECDSA encoding. - # Issue: https://github.com/w3c-ccg/http-signatures/issues/107 - signature = DSS.new(key=self.private_key, mode=sig_alg, - encoding='der').sign(digest) - else: - raise Exception("Unsupported signature algorithm: {0}".format(sig_alg)) - else: - raise Exception("Unsupported private key: {0}".format(type(self.private_key))) - return b64encode(signature) - - def _get_authorization_header(self, signed_headers, signed_msg): - """Calculates and returns the value of the 'Authorization' header when signing HTTP requests. - - :param signed_headers : A list of tuples. Each value is the name of a HTTP header that - must be included in the HTTP signature calculation. - :param signed_msg: A base-64 encoded string representation of the signature. - :return: The string value of the 'Authorization' header, representing the signature - of the HTTP request. - """ - created_ts = None - expires_ts = None - for k, v in signed_headers: - if k == HEADER_CREATED: - created_ts = v - elif k == HEADER_EXPIRES: - expires_ts = v - lower_keys = [k.lower() for k, v in signed_headers] - headers_value = " ".join(lower_keys) - - auth_str = "Signature keyId=\"{0}\",algorithm=\"{1}\",".format( - self.key_id, self.signing_scheme) - if created_ts is not None: - auth_str = auth_str + "created={0},".format(created_ts) - if expires_ts is not None: - auth_str = auth_str + "expires={0},".format(expires_ts) - auth_str = auth_str + "headers=\"{0}\",signature=\"{1}\"".format( - headers_value, signed_msg.decode('ascii')) - - return auth_str diff --git a/generator/src/main/resources/python-nextgen-custom-client/test-requirements.mustache b/generator/src/main/resources/python-nextgen-custom-client/test-requirements.mustache deleted file mode 100644 index ca8eb2770..000000000 --- a/generator/src/main/resources/python-nextgen-custom-client/test-requirements.mustache +++ /dev/null @@ -1,6 +0,0 @@ -pytest~=7.1.3 -pytest-cov>=2.8.1 -pytest-randomly>=3.12.0 -{{#hasHttpSignatureMethods}} -pycryptodome>=3.9.0 -{{/hasHttpSignatureMethods}} \ No newline at end of file diff --git a/generator/src/main/resources/python-nextgen-custom-client/tornado/rest.mustache b/generator/src/main/resources/python-nextgen-custom-client/tornado/rest.mustache deleted file mode 100644 index 59f14ffab..000000000 --- a/generator/src/main/resources/python-nextgen-custom-client/tornado/rest.mustache +++ /dev/null @@ -1,223 +0,0 @@ -# coding: utf-8 - -{{>partial_header}} - -import io -import json -import logging -import re - -from urllib.parse import urlencode, quote_plus -import tornado -import tornado.gen -from tornado import httpclient -from urllib3.filepost import encode_multipart_formdata - -from {{packageName}}.exceptions import ApiException, ApiValueError - -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: - 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 ApiValueError( - "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) diff --git a/generator/src/main/resources/python-nextgen-custom-client/tox.mustache b/generator/src/main/resources/python-nextgen-custom-client/tox.mustache deleted file mode 100644 index 9d717c3dd..000000000 --- a/generator/src/main/resources/python-nextgen-custom-client/tox.mustache +++ /dev/null @@ -1,9 +0,0 @@ -[tox] -envlist = py3 - -[testenv] -deps=-r{toxinidir}/requirements.txt - -r{toxinidir}/test-requirements.txt - -commands= - pytest --cov={{{packageName}}} diff --git a/generator/src/test/java/line/bot/generator/PythonNextgenCustomClientGeneratorTest.java b/generator/src/test/java/line/bot/generator/PythonNextgenCustomClientGeneratorTest.java index c4dcbcb17..96038e017 100644 --- a/generator/src/test/java/line/bot/generator/PythonNextgenCustomClientGeneratorTest.java +++ b/generator/src/test/java/line/bot/generator/PythonNextgenCustomClientGeneratorTest.java @@ -1,36 +1,50 @@ package line.bot.generator; -import org.junit.Test; +import org.junit.jupiter.api.Test; import org.openapitools.codegen.ClientOptInput; import org.openapitools.codegen.DefaultGenerator; import org.openapitools.codegen.config.CodegenConfigurator; +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.stream.Stream; + + /*** * This test allows you to easily launch your code generation software under a debugger. * Then run this test under debug mode. You will be able to step through your java code * and then see the results in the out directory. - * - * To experiment with debugging your code generator: - * 1) Set a break point in PythonNextgenCustomClientGenerator.java in the postProcessOperationsWithModels() method. - * 2) To launch this test in Eclipse: right-click | Debug As | JUnit Test - * */ public class PythonNextgenCustomClientGeneratorTest { + @Test + public void messagingApi() throws IOException { + generate("messaging-api"); + } + + private void generate(String target) throws IOException { + Path outPath = Paths.get("out/" + target); + if (outPath.toFile().exists()) { + try (Stream stream = Files.walk(outPath)) { + //noinspection ResultOfMethodCallIgnored + stream.map(Path::toFile) + .forEach(File::delete); + } + } - // use this test to launch you code generator in the debugger. - // this allows you to easily set break points in MyclientcodegenGenerator. - @Test - public void launchCodeGenerator() { - // to understand how the 'openapi-generator-cli' module is using 'CodegenConfigurator', have a look at the 'Generate' class: - // https://github.com/OpenAPITools/openapi-generator/blob/master/modules/openapi-generator-cli/src/main/java/org/openapitools/codegen/cmd/Generate.java - final CodegenConfigurator configurator = new CodegenConfigurator() - .setGeneratorName("python-nextgen-custom-client") // use this codegen library - .setInputSpec("../../../modules/openapi-generator/src/test/resources/2_0/petstore.yaml") // sample OpenAPI file - // .setInputSpec("https://raw.githubusercontent.com/openapitools/openapi-generator/master/modules/openapi-generator/src/test/resources/2_0/petstore.yaml") // or from the server - .setOutputDir("out/python-nextgen-custom-client"); // output directory + // to understand how the 'openapi-generator-cli' module is using 'CodegenConfigurator', have a look at the 'Generate' class: + // https://github.com/OpenAPITools/openapi-generator/blob/master/modules/openapi-generator-cli/src/main/java/org/openapitools/codegen/cmd/Generate.java + final CodegenConfigurator configurator = new CodegenConfigurator() + .setTemplatingEngineName("pebble") + .setTemplateDir("src/main/resources/python-nextgen-custom-client") + .setGeneratorName("python-nextgen-custom-client") // use this codegen library + .setInputSpec("../line-openapi/" + target + ".yml") // sample OpenAPI file + .setOutputDir("out/" + target); // output directory - final ClientOptInput clientOptInput = configurator.toClientOptInput(); - DefaultGenerator generator = new DefaultGenerator(); - generator.opts(clientOptInput).generate(); - } -} \ No newline at end of file + final ClientOptInput clientOptInput = configurator.toClientOptInput(); + DefaultGenerator generator = new DefaultGenerator(); + generator.opts(clientOptInput).generate(); + } +} diff --git a/linebot/v3/audience/docs/ManageAudience.md b/linebot/v3/audience/docs/ManageAudience.md index 5c1d65b7a..cedf13fc9 100644 --- a/linebot/v3/audience/docs/ManageAudience.md +++ b/linebot/v3/audience/docs/ManageAudience.md @@ -530,7 +530,7 @@ Name | Type | Description | Notes **status** | [**AudienceGroupStatus**](.md)| The status of the audience(s) to return. If omitted, the status of the audience(s) will not be used as a search criterion. | [optional] **size** | **int**| The number of audiences per page. Default: 20 Max: 40 | [optional] **includes_external_public_groups** | **bool**| true (default): Get public audiences created in all channels linked to the same bot. false: Get audiences created in the same channel. | [optional] - **create_route** | [**AudienceGroupCreateRoute**](.md)| How the audience was created. If omitted, all audiences are included. `OA_MANAGER`: Return only audiences created with LINE Official Account Manager (opens new window). `MESSAGING_API`: Return only audiences created with Messaging API. | [optional] + **create_route** | [**AudienceGroupCreateRoute**](.md)| How the audience was created. If omitted, all audiences are included. `OA_MANAGER`: Return only audiences created with LINE Official Account Manager (opens new window). `MESSAGING_API`: Return only audiences created with Messaging API. | [optional] ### Return type @@ -692,7 +692,7 @@ Name | Type | Description | Notes **description** | **str**| The name of the audience(s) to return. You can search for partial matches. This is case-insensitive, meaning AUDIENCE and audience are considered identical. If omitted, the name of the audience(s) will not be used as a search criterion. | [optional] **status** | [**AudienceGroupStatus**](.md)| The status of the audience(s) to return. If omitted, the status of the audience(s) will not be used as a search criterion. | [optional] **size** | **int**| The number of audiences per page. Default: 20 Max: 40 | [optional] - **create_route** | [**AudienceGroupCreateRoute**](.md)| How the audience was created. If omitted, all audiences are included. `OA_MANAGER`: Return only audiences created with LINE Official Account Manager (opens new window). `MESSAGING_API`: Return only audiences created with Messaging API. | [optional] + **create_route** | [**AudienceGroupCreateRoute**](.md)| How the audience was created. If omitted, all audiences are included. `OA_MANAGER`: Return only audiences created with LINE Official Account Manager (opens new window). `MESSAGING_API`: Return only audiences created with Messaging API. | [optional] **includes_owned_audience_groups** | **bool**| true: Include audienceGroups owned by LINE Official Account Manager false: Respond only audienceGroups shared by Business Manager | [optional] [default to False] ### Return type @@ -790,3 +790,4 @@ void (empty response body) [[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) + diff --git a/linebot/v3/audience/docs/ManageAudienceBlob.md b/linebot/v3/audience/docs/ManageAudienceBlob.md index 6e6e7a7e2..630fa1267 100644 --- a/linebot/v3/audience/docs/ManageAudienceBlob.md +++ b/linebot/v3/audience/docs/ManageAudienceBlob.md @@ -141,9 +141,9 @@ with linebot.v3.audience.ApiClient(configuration) as api_client: Name | Type | Description | Notes ------------- | ------------- | ------------- | ------------- **file** | **bytearray**| A text file with one user ID or IFA entered per line. Specify text/plain as Content-Type. Max file number: 1 Max number: 1,500,000 | - **description** | **str**| The audience's name. This is case-insensitive, meaning AUDIENCE and audience are considered identical. Max character limit: 120 | [optional] - **is_ifa_audience** | **bool**| To specify recipients by IFAs: set `true`. To specify recipients by user IDs: set `false` or omit isIfaAudience property. | [optional] - **upload_description** | **str**| The description to register for the job (in `jobs[].description`). | [optional] + **description** | **str**| The audience's name. This is case-insensitive, meaning AUDIENCE and audience are considered identical. Max character limit: 120 | [optional] + **is_ifa_audience** | **bool**| To specify recipients by IFAs: set `true`. To specify recipients by user IDs: set `false` or omit isIfaAudience property. | [optional] + **upload_description** | **str**| The description to register for the job (in `jobs[].description`). | [optional] ### Return type @@ -165,3 +165,4 @@ Name | Type | Description | Notes [[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) + diff --git a/linebot/v3/audience/models/audience_group_failed_type.py b/linebot/v3/audience/models/audience_group_failed_type.py index b6626cb9c..d4002840c 100644 --- a/linebot/v3/audience/models/audience_group_failed_type.py +++ b/linebot/v3/audience/models/audience_group_failed_type.py @@ -31,7 +31,6 @@ class AudienceGroupFailedType(str, Enum): """ AUDIENCE_GROUP_AUDIENCE_INSUFFICIENT = 'AUDIENCE_GROUP_AUDIENCE_INSUFFICIENT' INTERNAL_ERROR = 'INTERNAL_ERROR' - NULL = 'null' @classmethod def from_json(cls, json_str: str) -> AudienceGroupFailedType: diff --git a/linebot/v3/insight/docs/Insight.md b/linebot/v3/insight/docs/Insight.md index 417ca2b2c..554b5d6af 100644 --- a/linebot/v3/insight/docs/Insight.md +++ b/linebot/v3/insight/docs/Insight.md @@ -365,7 +365,7 @@ with linebot.v3.insight.ApiClient(configuration) as api_client: Name | Type | Description | Notes ------------- | ------------- | ------------- | ------------- - **custom_aggregation_unit** | **str**| Name of aggregation unit specified when sending the message. Case-sensitive. For example, `Promotion_a` and `Promotion_A` are regarded as different unit names. | + **custom_aggregation_unit** | **str**| Name of aggregation unit specified when sending the message. Case-sensitive. For example, `Promotion_a` and `Promotion_A` are regarded as different unit names. | **var_from** | **str**| Start date of aggregation period. Format: yyyyMMdd (e.g. 20210301) Time zone: UTC+9 | **to** | **str**| End date of aggregation period. The end date can be specified for up to 30 days later. For example, if the start date is 20210301, the latest end date is 20210331. Format: yyyyMMdd (e.g. 20210301) Time zone: UTC+9 | @@ -389,3 +389,4 @@ Name | Type | Description | Notes [[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) + diff --git a/linebot/v3/liff/docs/Liff.md b/linebot/v3/liff/docs/Liff.md index b1a34fa97..dcc8a4751 100644 --- a/linebot/v3/liff/docs/Liff.md +++ b/linebot/v3/liff/docs/Liff.md @@ -317,3 +317,4 @@ void (empty response body) [[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) + diff --git a/linebot/v3/messaging/docs/MessagingApi.md b/linebot/v3/messaging/docs/MessagingApi.md index f9dcc2486..29a80e0fb 100644 --- a/linebot/v3/messaging/docs/MessagingApi.md +++ b/linebot/v3/messaging/docs/MessagingApi.md @@ -129,7 +129,7 @@ with linebot.v3.messaging.ApiClient(configuration) as api_client: Name | Type | Description | Notes ------------- | ------------- | ------------- | ------------- **broadcast_request** | [**BroadcastRequest**](BroadcastRequest.md)| | - **x_line_retry_key** | **str**| Retry key. Specifies the UUID in hexadecimal format (e.g., `123e4567-e89b-12d3-a456-426614174000`) generated by any method. The retry key isn't generated by LINE. Each developer must generate their own retry key. | [optional] + **x_line_retry_key** | **str**| Retry key. Specifies the UUID in hexadecimal format (e.g., `123e4567-e89b-12d3-a456-426614174000`) generated by any method. The retry key isn't generated by LINE. Each developer must generate their own retry key. | [optional] ### Return type @@ -725,7 +725,7 @@ with linebot.v3.messaging.ApiClient(configuration) as api_client: Name | Type | Description | Notes ------------- | ------------- | ------------- | ------------- **limit** | **str**| The maximum number of aggregation units you can get per request. | [optional] - **start** | **str**| Value of the continuation token found in the next property of the JSON object returned in the response. If you can't get all the aggregation units in one request, include this parameter to get the remaining array. | [optional] + **start** | **str**| Value of the continuation token found in the next property of the JSON object returned in the response. If you can't get all the aggregation units in one request, include this parameter to get the remaining array. | [optional] ### Return type @@ -1321,7 +1321,7 @@ with linebot.v3.messaging.ApiClient(configuration) as api_client: Name | Type | Description | Notes ------------- | ------------- | ------------- | ------------- **group_id** | **str**| Group ID | - **start** | **str**| Value of the continuation token found in the `next` property of the JSON object returned in the response. Include this parameter to get the next array of user IDs for the members of the group. | [optional] + **start** | **str**| Value of the continuation token found in the `next` property of the JSON object returned in the response. Include this parameter to get the next array of user IDs for the members of the group. | [optional] ### Return type @@ -1474,7 +1474,7 @@ with linebot.v3.messaging.ApiClient(configuration) as api_client: Name | Type | Description | Notes ------------- | ------------- | ------------- | ------------- **membership_id** | **int**| Membership plan ID. | - **start** | **str**| A continuation token to get next remaining membership user IDs. Returned only when there are remaining user IDs that weren't returned in the userIds property in the previous request. The continuation token expires in 24 hours (86,400 seconds). | [optional] + **start** | **str**| A continuation token to get next remaining membership user IDs. Returned only when there are remaining user IDs that weren't returned in the userIds property in the previous request. The continuation token expires in 24 hours (86,400 seconds). | [optional] **limit** | **int**| The max number of items to return for this API call. The value is set to 300 by default, but the max acceptable value is 1000. | [optional] [default to 300] ### Return type @@ -1494,7 +1494,7 @@ Name | Type | Description | Notes | Status code | Description | Response headers | |-------------|-------------|------------------| **200** | OK | - | -**400** | `start` is incorrect or expired, or `limit` is under 1 or over 1000. | - | +**400** | `start` is incorrect or expired, or `limit` is under 1 or over 1000. | - | **404** | Membership ID is not owned by the bot or does not exist. | - | [[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) @@ -1843,7 +1843,7 @@ with linebot.v3.messaging.ApiClient(configuration) as api_client: Name | Type | Description | Notes ------------- | ------------- | ------------- | ------------- - **request_id** | **str**| The narrowcast message's request ID. Each Messaging API request has a request ID. | + **request_id** | **str**| The narrowcast message's request ID. Each Messaging API request has a request ID. | ### Return type @@ -1993,7 +1993,7 @@ with linebot.v3.messaging.ApiClient(configuration) as api_client: Name | Type | Description | Notes ------------- | ------------- | ------------- | ------------- - **var_date** | **str**| Date the messages were sent Format: `yyyyMMdd` (e.g. `20191231`) Timezone: UTC+9 | + **var_date** | **str**| Date the messages were sent Format: `yyyyMMdd` (e.g. `20191231`) Timezone: UTC+9 | ### Return type @@ -2068,7 +2068,7 @@ with linebot.v3.messaging.ApiClient(configuration) as api_client: Name | Type | Description | Notes ------------- | ------------- | ------------- | ------------- - **var_date** | **str**| Date the messages were sent Format: `yyyyMMdd` (e.g. `20191231`) Timezone: UTC+9 | + **var_date** | **str**| Date the messages were sent Format: `yyyyMMdd` (e.g. `20191231`) Timezone: UTC+9 | ### Return type @@ -2143,7 +2143,7 @@ with linebot.v3.messaging.ApiClient(configuration) as api_client: Name | Type | Description | Notes ------------- | ------------- | ------------- | ------------- - **var_date** | **str**| Date the messages were sent Format: `yyyyMMdd` (e.g. `20191231`) Timezone: UTC+9 | + **var_date** | **str**| Date the messages were sent Format: `yyyyMMdd` (e.g. `20191231`) Timezone: UTC+9 | ### Return type @@ -2218,7 +2218,7 @@ with linebot.v3.messaging.ApiClient(configuration) as api_client: Name | Type | Description | Notes ------------- | ------------- | ------------- | ------------- - **var_date** | **str**| Date the message was sent Format: `yyyyMMdd` (Example:`20211231`) Time zone: UTC+9 | + **var_date** | **str**| Date the message was sent Format: `yyyyMMdd` (Example:`20211231`) Time zone: UTC+9 | ### Return type @@ -2664,7 +2664,7 @@ with linebot.v3.messaging.ApiClient(configuration) as api_client: Name | Type | Description | Notes ------------- | ------------- | ------------- | ------------- - **user_id** | **str**| User ID. Found in the `source` object of webhook event objects. Do not use the LINE ID used in LINE. | + **user_id** | **str**| User ID. Found in the `source` object of webhook event objects. Do not use the LINE ID used in LINE. | ### Return type @@ -2964,7 +2964,7 @@ with linebot.v3.messaging.ApiClient(configuration) as api_client: Name | Type | Description | Notes ------------- | ------------- | ------------- | ------------- **room_id** | **str**| Room ID | - **start** | **str**| Value of the continuation token found in the `next` property of the JSON object returned in the response. Include this parameter to get the next array of user IDs for the members of the group. | [optional] + **start** | **str**| Value of the continuation token found in the `next` property of the JSON object returned in the response. Include this parameter to get the next array of user IDs for the members of the group. | [optional] ### Return type @@ -3110,7 +3110,7 @@ with linebot.v3.messaging.ApiClient(configuration) as api_client: Name | Type | Description | Notes ------------- | ------------- | ------------- | ------------- - **user_id** | **str**| User ID for the LINE account to be linked. Found in the `source` object of account link event objects. Do not use the LINE ID used in LINE. | + **user_id** | **str**| User ID for the LINE account to be linked. Found in the `source` object of account link event objects. Do not use the LINE ID used in LINE. | ### Return type @@ -3329,7 +3329,7 @@ with linebot.v3.messaging.ApiClient(configuration) as api_client: Name | Type | Description | Notes ------------- | ------------- | ------------- | ------------- - **user_id** | **str**| User ID. Found in the `source` object of webhook event objects. Do not use the LINE ID used in LINE. | + **user_id** | **str**| User ID. Found in the `source` object of webhook event objects. Do not use the LINE ID used in LINE. | **rich_menu_id** | **str**| ID of a rich menu | ### Return type @@ -3707,7 +3707,7 @@ with linebot.v3.messaging.ApiClient(configuration) as api_client: Name | Type | Description | Notes ------------- | ------------- | ------------- | ------------- **multicast_request** | [**MulticastRequest**](MulticastRequest.md)| | - **x_line_retry_key** | **str**| Retry key. Specifies the UUID in hexadecimal format (e.g., `123e4567-e89b-12d3-a456-426614174000`) generated by any method. The retry key isn't generated by LINE. Each developer must generate their own retry key. | [optional] + **x_line_retry_key** | **str**| Retry key. Specifies the UUID in hexadecimal format (e.g., `123e4567-e89b-12d3-a456-426614174000`) generated by any method. The retry key isn't generated by LINE. Each developer must generate their own retry key. | [optional] ### Return type @@ -3788,7 +3788,7 @@ with linebot.v3.messaging.ApiClient(configuration) as api_client: Name | Type | Description | Notes ------------- | ------------- | ------------- | ------------- **narrowcast_request** | [**NarrowcastRequest**](NarrowcastRequest.md)| | - **x_line_retry_key** | **str**| Retry key. Specifies the UUID in hexadecimal format (e.g., `123e4567-e89b-12d3-a456-426614174000`) generated by any method. The retry key isn't generated by LINE. Each developer must generate their own retry key. | [optional] + **x_line_retry_key** | **str**| Retry key. Specifies the UUID in hexadecimal format (e.g., `123e4567-e89b-12d3-a456-426614174000`) generated by any method. The retry key isn't generated by LINE. Each developer must generate their own retry key. | [optional] ### Return type @@ -3870,7 +3870,7 @@ with linebot.v3.messaging.ApiClient(configuration) as api_client: Name | Type | Description | Notes ------------- | ------------- | ------------- | ------------- **push_message_request** | [**PushMessageRequest**](PushMessageRequest.md)| | - **x_line_retry_key** | **str**| Retry key. Specifies the UUID in hexadecimal format (e.g., `123e4567-e89b-12d3-a456-426614174000`) generated by any method. The retry key isn't generated by LINE. Each developer must generate their own retry key. | [optional] + **x_line_retry_key** | **str**| Retry key. Specifies the UUID in hexadecimal format (e.g., `123e4567-e89b-12d3-a456-426614174000`) generated by any method. The retry key isn't generated by LINE. Each developer must generate their own retry key. | [optional] ### Return type @@ -4470,7 +4470,7 @@ with linebot.v3.messaging.ApiClient(configuration) as api_client: Name | Type | Description | Notes ------------- | ------------- | ------------- | ------------- - **user_id** | **str**| User ID. Found in the `source` object of webhook event objects. Do not use the LINE ID used in LINE. | + **user_id** | **str**| User ID. Found in the `source` object of webhook event objects. Do not use the LINE ID used in LINE. | ### Return type @@ -5152,3 +5152,4 @@ void (empty response body) [[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) + diff --git a/linebot/v3/messaging/docs/MessagingApiBlob.md b/linebot/v3/messaging/docs/MessagingApiBlob.md index 87fa7b0a6..c6d534822 100644 --- a/linebot/v3/messaging/docs/MessagingApiBlob.md +++ b/linebot/v3/messaging/docs/MessagingApiBlob.md @@ -382,3 +382,4 @@ void (empty response body) [[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) + diff --git a/linebot/v3/module/docs/LineModule.md b/linebot/v3/module/docs/LineModule.md index 479367a06..88d20b32e 100644 --- a/linebot/v3/module/docs/LineModule.md +++ b/linebot/v3/module/docs/LineModule.md @@ -62,7 +62,7 @@ with linebot.v3.module.ApiClient(configuration) as api_client: Name | Type | Description | Notes ------------- | ------------- | ------------- | ------------- - **chat_id** | **str**| The `userId`, `roomId`, or `groupId` | + **chat_id** | **str**| The `userId`, `roomId`, or `groupId` | **acquire_chat_control_request** | [**AcquireChatControlRequest**](AcquireChatControlRequest.md)| | [optional] ### Return type @@ -212,7 +212,7 @@ with linebot.v3.module.ApiClient(configuration) as api_client: Name | Type | Description | Notes ------------- | ------------- | ------------- | ------------- - **start** | **str**| Value of the continuation token found in the next property of the JSON object returned in the response. If you can't get all basic information about the bots in one request, include this parameter to get the remaining array. | [optional] + **start** | **str**| Value of the continuation token found in the next property of the JSON object returned in the response. If you can't get all basic information about the bots in one request, include this parameter to get the remaining array. | [optional] **limit** | **int**| Specify the maximum number of bots that you get basic information from. The default value is 100. Max value: 100 | [optional] [default to 100] ### Return type @@ -285,7 +285,7 @@ with linebot.v3.module.ApiClient(configuration) as api_client: Name | Type | Description | Notes ------------- | ------------- | ------------- | ------------- - **chat_id** | **str**| The `userId`, `roomId`, or `groupId` | + **chat_id** | **str**| The `userId`, `roomId`, or `groupId` | ### Return type @@ -307,3 +307,4 @@ void (empty response body) [[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) + diff --git a/linebot/v3/moduleattach/docs/LineModuleAttach.md b/linebot/v3/moduleattach/docs/LineModuleAttach.md index da22e7cd6..7aaf0a59c 100644 --- a/linebot/v3/moduleattach/docs/LineModuleAttach.md +++ b/linebot/v3/moduleattach/docs/LineModuleAttach.md @@ -42,6 +42,7 @@ configuration = linebot.v3.moduleattach.Configuration( password = os.environ["PASSWORD"] ) + # Enter a context with an instance of the API client with linebot.v3.moduleattach.ApiClient(configuration) as api_client: # Create an instance of the API class @@ -101,3 +102,4 @@ Name | Type | Description | Notes [[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) + diff --git a/linebot/v3/oauth/api/async_channel_access_token.py b/linebot/v3/oauth/api/async_channel_access_token.py index dd1c2b60f..cad33385a 100644 --- a/linebot/v3/oauth/api/async_channel_access_token.py +++ b/linebot/v3/oauth/api/async_channel_access_token.py @@ -22,6 +22,8 @@ from pydantic.v1 import Field, StrictStr +from typing import Optional + from linebot.v3.oauth.models.channel_access_token_key_ids_response import ChannelAccessTokenKeyIdsResponse from linebot.v3.oauth.models.issue_channel_access_token_response import IssueChannelAccessTokenResponse from linebot.v3.oauth.models.issue_short_lived_channel_access_token_response import IssueShortLivedChannelAccessTokenResponse @@ -559,15 +561,15 @@ def issue_channel_token_by_jwt_with_http_info(self, grant_type : Annotated[Stric _request_auth=_params.get('_request_auth')) @overload - async def issue_stateless_channel_token(self, grant_type : Annotated[StrictStr, Field(..., description="`client_credentials`")], client_assertion_type : Annotated[StrictStr, Field(..., description="URL-encoded value of `urn:ietf:params:oauth:client-assertion-type:jwt-bearer`")], client_assertion : Annotated[StrictStr, Field(..., description="A JSON Web Token the client needs to create and sign with the private key of the Assertion Signing Key.")], client_id : Annotated[StrictStr, Field(..., description="Channel ID.")], client_secret : Annotated[StrictStr, Field(..., description="Channel secret.")], **kwargs) -> IssueStatelessChannelAccessTokenResponse: # noqa: E501 + async def issue_stateless_channel_token(self, grant_type : Annotated[Optional[StrictStr], Field(description="`client_credentials`")] = None, client_assertion_type : Annotated[Optional[StrictStr], Field(description="URL-encoded value of `urn:ietf:params:oauth:client-assertion-type:jwt-bearer`")] = None, client_assertion : Annotated[Optional[StrictStr], Field(description="A JSON Web Token the client needs to create and sign with the private key of the Assertion Signing Key.")] = None, client_id : Annotated[Optional[StrictStr], Field(description="Channel ID.")] = None, client_secret : Annotated[Optional[StrictStr], Field(description="Channel secret.")] = None, **kwargs) -> IssueStatelessChannelAccessTokenResponse: # noqa: E501 ... @overload - def issue_stateless_channel_token(self, grant_type : Annotated[StrictStr, Field(..., description="`client_credentials`")], client_assertion_type : Annotated[StrictStr, Field(..., description="URL-encoded value of `urn:ietf:params:oauth:client-assertion-type:jwt-bearer`")], client_assertion : Annotated[StrictStr, Field(..., description="A JSON Web Token the client needs to create and sign with the private key of the Assertion Signing Key.")], client_id : Annotated[StrictStr, Field(..., description="Channel ID.")], client_secret : Annotated[StrictStr, Field(..., description="Channel secret.")], async_req: Optional[bool]=True, **kwargs) -> IssueStatelessChannelAccessTokenResponse: # noqa: E501 + def issue_stateless_channel_token(self, grant_type : Annotated[Optional[StrictStr], Field(description="`client_credentials`")] = None, client_assertion_type : Annotated[Optional[StrictStr], Field(description="URL-encoded value of `urn:ietf:params:oauth:client-assertion-type:jwt-bearer`")] = None, client_assertion : Annotated[Optional[StrictStr], Field(description="A JSON Web Token the client needs to create and sign with the private key of the Assertion Signing Key.")] = None, client_id : Annotated[Optional[StrictStr], Field(description="Channel ID.")] = None, client_secret : Annotated[Optional[StrictStr], Field(description="Channel secret.")] = None, async_req: Optional[bool]=True, **kwargs) -> IssueStatelessChannelAccessTokenResponse: # noqa: E501 ... @validate_arguments - def issue_stateless_channel_token(self, grant_type : Annotated[StrictStr, Field(..., description="`client_credentials`")], client_assertion_type : Annotated[StrictStr, Field(..., description="URL-encoded value of `urn:ietf:params:oauth:client-assertion-type:jwt-bearer`")], client_assertion : Annotated[StrictStr, Field(..., description="A JSON Web Token the client needs to create and sign with the private key of the Assertion Signing Key.")], client_id : Annotated[StrictStr, Field(..., description="Channel ID.")], client_secret : Annotated[StrictStr, Field(..., description="Channel secret.")], async_req: Optional[bool]=None, **kwargs) -> Union[IssueStatelessChannelAccessTokenResponse, Awaitable[IssueStatelessChannelAccessTokenResponse]]: # noqa: E501 + def issue_stateless_channel_token(self, grant_type : Annotated[Optional[StrictStr], Field(description="`client_credentials`")] = None, client_assertion_type : Annotated[Optional[StrictStr], Field(description="URL-encoded value of `urn:ietf:params:oauth:client-assertion-type:jwt-bearer`")] = None, client_assertion : Annotated[Optional[StrictStr], Field(description="A JSON Web Token the client needs to create and sign with the private key of the Assertion Signing Key.")] = None, client_id : Annotated[Optional[StrictStr], Field(description="Channel ID.")] = None, client_secret : Annotated[Optional[StrictStr], Field(description="Channel secret.")] = None, async_req: Optional[bool]=None, **kwargs) -> Union[IssueStatelessChannelAccessTokenResponse, Awaitable[IssueStatelessChannelAccessTokenResponse]]: # noqa: E501 """issue_stateless_channel_token # noqa: E501 .. deprecated:: @@ -581,15 +583,15 @@ def issue_stateless_channel_token(self, grant_type : Annotated[StrictStr, Field( >>> thread = api.issue_stateless_channel_token(grant_type, client_assertion_type, client_assertion, client_id, client_secret, async_req=True) >>> result = thread.get() - :param grant_type: `client_credentials` (required) + :param grant_type: `client_credentials` :type grant_type: str - :param client_assertion_type: URL-encoded value of `urn:ietf:params:oauth:client-assertion-type:jwt-bearer` (required) + :param client_assertion_type: URL-encoded value of `urn:ietf:params:oauth:client-assertion-type:jwt-bearer` :type client_assertion_type: str - :param client_assertion: A JSON Web Token the client needs to create and sign with the private key of the Assertion Signing Key. (required) + :param client_assertion: A JSON Web Token the client needs to create and sign with the private key of the Assertion Signing Key. :type client_assertion: str - :param client_id: Channel ID. (required) + :param client_id: Channel ID. :type client_id: str - :param client_secret: Channel secret. (required) + :param client_secret: Channel secret. :type client_secret: str :param async_req: Whether to execute the request asynchronously. :type async_req: bool, optional @@ -610,7 +612,7 @@ def issue_stateless_channel_token(self, grant_type : Annotated[StrictStr, Field( return self.issue_stateless_channel_token_with_http_info(grant_type, client_assertion_type, client_assertion, client_id, client_secret, **kwargs) # noqa: E501 @validate_arguments - def issue_stateless_channel_token_with_http_info(self, grant_type : Annotated[StrictStr, Field(..., description="`client_credentials`")], client_assertion_type : Annotated[StrictStr, Field(..., description="URL-encoded value of `urn:ietf:params:oauth:client-assertion-type:jwt-bearer`")], client_assertion : Annotated[StrictStr, Field(..., description="A JSON Web Token the client needs to create and sign with the private key of the Assertion Signing Key.")], client_id : Annotated[StrictStr, Field(..., description="Channel ID.")], client_secret : Annotated[StrictStr, Field(..., description="Channel secret.")], **kwargs) -> ApiResponse: # noqa: E501 + def issue_stateless_channel_token_with_http_info(self, grant_type : Annotated[Optional[StrictStr], Field(description="`client_credentials`")] = None, client_assertion_type : Annotated[Optional[StrictStr], Field(description="URL-encoded value of `urn:ietf:params:oauth:client-assertion-type:jwt-bearer`")] = None, client_assertion : Annotated[Optional[StrictStr], Field(description="A JSON Web Token the client needs to create and sign with the private key of the Assertion Signing Key.")] = None, client_id : Annotated[Optional[StrictStr], Field(description="Channel ID.")] = None, client_secret : Annotated[Optional[StrictStr], Field(description="Channel secret.")] = None, **kwargs) -> ApiResponse: # noqa: E501 """issue_stateless_channel_token # noqa: E501 .. deprecated:: @@ -624,15 +626,15 @@ def issue_stateless_channel_token_with_http_info(self, grant_type : Annotated[St >>> thread = api.issue_stateless_channel_token_with_http_info(grant_type, client_assertion_type, client_assertion, client_id, client_secret, async_req=True) >>> result = thread.get() - :param grant_type: `client_credentials` (required) + :param grant_type: `client_credentials` :type grant_type: str - :param client_assertion_type: URL-encoded value of `urn:ietf:params:oauth:client-assertion-type:jwt-bearer` (required) + :param client_assertion_type: URL-encoded value of `urn:ietf:params:oauth:client-assertion-type:jwt-bearer` :type client_assertion_type: str - :param client_assertion: A JSON Web Token the client needs to create and sign with the private key of the Assertion Signing Key. (required) + :param client_assertion: A JSON Web Token the client needs to create and sign with the private key of the Assertion Signing Key. :type client_assertion: str - :param client_id: Channel ID. (required) + :param client_id: Channel ID. :type client_id: str - :param client_secret: Channel secret. (required) + :param client_secret: Channel secret. :type client_secret: str :param async_req: Whether to execute the request asynchronously. :type async_req: bool, optional diff --git a/linebot/v3/oauth/api/channel_access_token.py b/linebot/v3/oauth/api/channel_access_token.py index e1c1af7c5..9fbc48330 100644 --- a/linebot/v3/oauth/api/channel_access_token.py +++ b/linebot/v3/oauth/api/channel_access_token.py @@ -20,6 +20,8 @@ from pydantic.v1 import Field, StrictStr +from typing import Optional + from linebot.v3.oauth.models.channel_access_token_key_ids_response import ChannelAccessTokenKeyIdsResponse from linebot.v3.oauth.models.issue_channel_access_token_response import IssueChannelAccessTokenResponse from linebot.v3.oauth.models.issue_short_lived_channel_access_token_response import IssueShortLivedChannelAccessTokenResponse @@ -527,7 +529,7 @@ def issue_channel_token_by_jwt_with_http_info(self, grant_type : Annotated[Stric _request_auth=_params.get('_request_auth')) @validate_arguments - def issue_stateless_channel_token(self, grant_type : Annotated[StrictStr, Field(..., description="`client_credentials`")], client_assertion_type : Annotated[StrictStr, Field(..., description="URL-encoded value of `urn:ietf:params:oauth:client-assertion-type:jwt-bearer`")], client_assertion : Annotated[StrictStr, Field(..., description="A JSON Web Token the client needs to create and sign with the private key of the Assertion Signing Key.")], client_id : Annotated[StrictStr, Field(..., description="Channel ID.")], client_secret : Annotated[StrictStr, Field(..., description="Channel secret.")], **kwargs) -> IssueStatelessChannelAccessTokenResponse: # noqa: E501 + def issue_stateless_channel_token(self, grant_type : Annotated[Optional[StrictStr], Field(description="`client_credentials`")] = None, client_assertion_type : Annotated[Optional[StrictStr], Field(description="URL-encoded value of `urn:ietf:params:oauth:client-assertion-type:jwt-bearer`")] = None, client_assertion : Annotated[Optional[StrictStr], Field(description="A JSON Web Token the client needs to create and sign with the private key of the Assertion Signing Key.")] = None, client_id : Annotated[Optional[StrictStr], Field(description="Channel ID.")] = None, client_secret : Annotated[Optional[StrictStr], Field(description="Channel secret.")] = None, **kwargs) -> IssueStatelessChannelAccessTokenResponse: # noqa: E501 """issue_stateless_channel_token # noqa: E501 .. deprecated:: @@ -541,15 +543,15 @@ def issue_stateless_channel_token(self, grant_type : Annotated[StrictStr, Field( >>> thread = api.issue_stateless_channel_token(grant_type, client_assertion_type, client_assertion, client_id, client_secret, async_req=True) >>> result = thread.get() - :param grant_type: `client_credentials` (required) + :param grant_type: `client_credentials` :type grant_type: str - :param client_assertion_type: URL-encoded value of `urn:ietf:params:oauth:client-assertion-type:jwt-bearer` (required) + :param client_assertion_type: URL-encoded value of `urn:ietf:params:oauth:client-assertion-type:jwt-bearer` :type client_assertion_type: str - :param client_assertion: A JSON Web Token the client needs to create and sign with the private key of the Assertion Signing Key. (required) + :param client_assertion: A JSON Web Token the client needs to create and sign with the private key of the Assertion Signing Key. :type client_assertion: str - :param client_id: Channel ID. (required) + :param client_id: Channel ID. :type client_id: str - :param client_secret: Channel secret. (required) + :param client_secret: Channel secret. :type client_secret: str :param async_req: Whether to execute the request asynchronously. :type async_req: bool, optional @@ -568,7 +570,7 @@ def issue_stateless_channel_token(self, grant_type : Annotated[StrictStr, Field( return self.issue_stateless_channel_token_with_http_info(grant_type, client_assertion_type, client_assertion, client_id, client_secret, **kwargs) # noqa: E501 @validate_arguments - def issue_stateless_channel_token_with_http_info(self, grant_type : Annotated[StrictStr, Field(..., description="`client_credentials`")], client_assertion_type : Annotated[StrictStr, Field(..., description="URL-encoded value of `urn:ietf:params:oauth:client-assertion-type:jwt-bearer`")], client_assertion : Annotated[StrictStr, Field(..., description="A JSON Web Token the client needs to create and sign with the private key of the Assertion Signing Key.")], client_id : Annotated[StrictStr, Field(..., description="Channel ID.")], client_secret : Annotated[StrictStr, Field(..., description="Channel secret.")], **kwargs) -> ApiResponse: # noqa: E501 + def issue_stateless_channel_token_with_http_info(self, grant_type : Annotated[Optional[StrictStr], Field(description="`client_credentials`")] = None, client_assertion_type : Annotated[Optional[StrictStr], Field(description="URL-encoded value of `urn:ietf:params:oauth:client-assertion-type:jwt-bearer`")] = None, client_assertion : Annotated[Optional[StrictStr], Field(description="A JSON Web Token the client needs to create and sign with the private key of the Assertion Signing Key.")] = None, client_id : Annotated[Optional[StrictStr], Field(description="Channel ID.")] = None, client_secret : Annotated[Optional[StrictStr], Field(description="Channel secret.")] = None, **kwargs) -> ApiResponse: # noqa: E501 """issue_stateless_channel_token # noqa: E501 .. deprecated:: @@ -582,15 +584,15 @@ def issue_stateless_channel_token_with_http_info(self, grant_type : Annotated[St >>> thread = api.issue_stateless_channel_token_with_http_info(grant_type, client_assertion_type, client_assertion, client_id, client_secret, async_req=True) >>> result = thread.get() - :param grant_type: `client_credentials` (required) + :param grant_type: `client_credentials` :type grant_type: str - :param client_assertion_type: URL-encoded value of `urn:ietf:params:oauth:client-assertion-type:jwt-bearer` (required) + :param client_assertion_type: URL-encoded value of `urn:ietf:params:oauth:client-assertion-type:jwt-bearer` :type client_assertion_type: str - :param client_assertion: A JSON Web Token the client needs to create and sign with the private key of the Assertion Signing Key. (required) + :param client_assertion: A JSON Web Token the client needs to create and sign with the private key of the Assertion Signing Key. :type client_assertion: str - :param client_id: Channel ID. (required) + :param client_id: Channel ID. :type client_id: str - :param client_secret: Channel secret. (required) + :param client_secret: Channel secret. :type client_secret: str :param async_req: Whether to execute the request asynchronously. :type async_req: bool, optional diff --git a/linebot/v3/oauth/docs/ChannelAccessToken.md b/linebot/v3/oauth/docs/ChannelAccessToken.md index 920ac9521..2155b8829 100644 --- a/linebot/v3/oauth/docs/ChannelAccessToken.md +++ b/linebot/v3/oauth/docs/ChannelAccessToken.md @@ -38,6 +38,7 @@ configuration = linebot.v3.oauth.Configuration( ) + # Enter a context with an instance of the API client with linebot.v3.oauth.ApiClient(configuration) as api_client: # Create an instance of the API class @@ -58,7 +59,7 @@ with linebot.v3.oauth.ApiClient(configuration) as api_client: Name | Type | Description | Notes ------------- | ------------- | ------------- | ------------- - **client_assertion_type** | **str**| `urn:ietf:params:oauth:client-assertion-type:jwt-bearer` | + **client_assertion_type** | **str**| `urn:ietf:params:oauth:client-assertion-type:jwt-bearer` | **client_assertion** | **str**| A JSON Web Token (JWT) (opens new window)the client needs to create and sign with the private key. | ### Return type @@ -105,6 +106,7 @@ configuration = linebot.v3.oauth.Configuration( ) + # Enter a context with an instance of the API client with linebot.v3.oauth.ApiClient(configuration) as api_client: # Create an instance of the API class @@ -126,7 +128,7 @@ with linebot.v3.oauth.ApiClient(configuration) as api_client: Name | Type | Description | Notes ------------- | ------------- | ------------- | ------------- - **grant_type** | **str**| `client_credentials` | + **grant_type** | **str**| `client_credentials` | **client_id** | **str**| Channel ID. | **client_secret** | **str**| Channel secret. | @@ -175,6 +177,7 @@ configuration = linebot.v3.oauth.Configuration( ) + # Enter a context with an instance of the API client with linebot.v3.oauth.ApiClient(configuration) as api_client: # Create an instance of the API class @@ -221,7 +224,7 @@ No authorization required [[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) # **issue_stateless_channel_token** -> IssueStatelessChannelAccessTokenResponse issue_stateless_channel_token(grant_type, client_assertion_type, client_assertion, client_id, client_secret) +> IssueStatelessChannelAccessTokenResponse issue_stateless_channel_token(grant_type=grant_type, client_assertion_type=client_assertion_type, client_assertion=client_assertion, client_id=client_id, client_secret=client_secret) @@ -244,18 +247,19 @@ configuration = linebot.v3.oauth.Configuration( ) + # Enter a context with an instance of the API client with linebot.v3.oauth.ApiClient(configuration) as api_client: # Create an instance of the API class api_instance = linebot.v3.oauth.ChannelAccessToken(api_client) - grant_type = 'grant_type_example' # str | `client_credentials` - client_assertion_type = 'client_assertion_type_example' # str | URL-encoded value of `urn:ietf:params:oauth:client-assertion-type:jwt-bearer` - client_assertion = 'client_assertion_example' # str | A JSON Web Token the client needs to create and sign with the private key of the Assertion Signing Key. - client_id = 'client_id_example' # str | Channel ID. - client_secret = 'client_secret_example' # str | Channel secret. + grant_type = 'grant_type_example' # str | `client_credentials` (optional) + client_assertion_type = 'client_assertion_type_example' # str | URL-encoded value of `urn:ietf:params:oauth:client-assertion-type:jwt-bearer` (optional) + client_assertion = 'client_assertion_example' # str | A JSON Web Token the client needs to create and sign with the private key of the Assertion Signing Key. (optional) + client_id = 'client_id_example' # str | Channel ID. (optional) + client_secret = 'client_secret_example' # str | Channel secret. (optional) try: - api_response = api_instance.issue_stateless_channel_token(grant_type, client_assertion_type, client_assertion, client_id, client_secret) + api_response = api_instance.issue_stateless_channel_token(grant_type=grant_type, client_assertion_type=client_assertion_type, client_assertion=client_assertion, client_id=client_id, client_secret=client_secret) print("The response of ChannelAccessToken->issue_stateless_channel_token:\n") pprint(api_response) except Exception as e: @@ -267,11 +271,11 @@ with linebot.v3.oauth.ApiClient(configuration) as api_client: Name | Type | Description | Notes ------------- | ------------- | ------------- | ------------- - **grant_type** | **str**| `client_credentials` | - **client_assertion_type** | **str**| URL-encoded value of `urn:ietf:params:oauth:client-assertion-type:jwt-bearer` | - **client_assertion** | **str**| A JSON Web Token the client needs to create and sign with the private key of the Assertion Signing Key. | - **client_id** | **str**| Channel ID. | - **client_secret** | **str**| Channel secret. | + **grant_type** | **str**| `client_credentials` | [optional] + **client_assertion_type** | **str**| URL-encoded value of `urn:ietf:params:oauth:client-assertion-type:jwt-bearer` | [optional] + **client_assertion** | **str**| A JSON Web Token the client needs to create and sign with the private key of the Assertion Signing Key. | [optional] + **client_id** | **str**| Channel ID. | [optional] + **client_secret** | **str**| Channel secret. | [optional] ### Return type @@ -316,6 +320,7 @@ configuration = linebot.v3.oauth.Configuration( ) + # Enter a context with an instance of the API client with linebot.v3.oauth.ApiClient(configuration) as api_client: # Create an instance of the API class @@ -378,6 +383,7 @@ configuration = linebot.v3.oauth.Configuration( ) + # Enter a context with an instance of the API client with linebot.v3.oauth.ApiClient(configuration) as api_client: # Create an instance of the API class @@ -445,6 +451,7 @@ configuration = linebot.v3.oauth.Configuration( ) + # Enter a context with an instance of the API client with linebot.v3.oauth.ApiClient(configuration) as api_client: # Create an instance of the API class @@ -510,6 +517,7 @@ configuration = linebot.v3.oauth.Configuration( ) + # Enter a context with an instance of the API client with linebot.v3.oauth.ApiClient(configuration) as api_client: # Create an instance of the API class @@ -551,3 +559,4 @@ No authorization required [[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) + diff --git a/linebot/v3/shop/docs/Shop.md b/linebot/v3/shop/docs/Shop.md index f891b717d..da98c2fe6 100644 --- a/linebot/v3/shop/docs/Shop.md +++ b/linebot/v3/shop/docs/Shop.md @@ -80,3 +80,4 @@ void (empty response body) [[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) + diff --git a/tools/openapi-generator-cli.jar b/tools/openapi-generator-cli.jar deleted file mode 100644 index 4dc71f2f4..000000000 Binary files a/tools/openapi-generator-cli.jar and /dev/null differ