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