Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -724,6 +724,11 @@ public OperationsMap postProcessOperationsWithModels(OperationsMap objs, List<Mo
}
}
}
for (CodegenParameter param : operation.allParams) {
if ((param.isFreeFormObject || param.isAnyType) && "object".equals(param.dataType)) {
param.vendorExtensions.put("x-python-connexion-skip-from-dict", Boolean.TRUE);
}
}
}
return objs;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,9 @@ async def {{operationId}}(request: web.Request, {{#allParams}}{{paramName}}{{^re
{{^isPrimitiveType}}
{{^isFile}}
{{^isUuid}}
{{^vendorExtensions.x-python-connexion-skip-from-dict}}
{{paramName}} = {{baseType}}.from_dict({{paramName}})
{{/vendorExtensions.x-python-connexion-skip-from-dict}}
{{/isUuid}}
{{/isFile}}
{{/isPrimitiveType}}
Expand All @@ -88,13 +90,16 @@ async def {{operationId}}(request: web.Request, {{#allParams}}{{paramName}}{{^re
{{#isMap}}
{{#items}}
{{#isDate}}
{{paramName}} = {k: util.deserialize_date(v) for k, v in {{paramName}}}
if {{paramName}} is not None:
{{paramName}} = {k: util.deserialize_date(v) for k, v in {{paramName}}.items()}
{{/isDate}}
{{#isDateTime}}
{{paramName}} = {k: util.deserialize_datetime(v) for k, v in {{paramName}}}
if {{paramName}} is not None:
{{paramName}} = {k: util.deserialize_datetime(v) for k, v in {{paramName}}.items()}
{{/isDateTime}}
{{#complexType}}
{{paramName}} = {k: {{baseType}}.from_dict(v) for k, v in {{paramName}}}
if {{paramName}} is not None:
{{paramName}} = {k: {{baseType}}.from_dict(v) for k, v in {{paramName}}.items()}
{{/complexType}}
{{/items}}
{{/isMap}}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,10 @@ def {{operationId}}({{#allParams}}{{paramName}}{{^required}}=None{{/required}}{{
{{^isPrimitiveType}}
{{^isFile}}
{{^isUuid}}
{{^vendorExtensions.x-python-connexion-skip-from-dict}}
if connexion.request.is_json:
{{paramName}} = {{baseType}}.from_dict(connexion.request.get_json()) # noqa: E501
{{/vendorExtensions.x-python-connexion-skip-from-dict}}
{{/isUuid}}
{{/isFile}}
{{/isPrimitiveType}}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,10 @@ def {{operationId}}({{#allParams}}{{^isBodyParam}}{{paramName}}{{/isBodyParam}}{
{{^isPrimitiveType}}
{{^isFile}}
{{^isUuid}}
{{^vendorExtensions.x-python-connexion-skip-from-dict}}
if flask.request.is_json:
{{paramName}} = {{baseType}}{{^baseType}}{{#dataType}} {{.}}{{/dataType}}{{/baseType}}.from_dict(flask.request.get_json()) # noqa: E501
{{/vendorExtensions.x-python-connexion-skip-from-dict}}
{{/isUuid}}
{{/isFile}}
{{/isPrimitiveType}}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,36 @@
package org.openapitools.codegen.python;

import com.google.common.collect.ImmutableMap;
import io.swagger.parser.OpenAPIParser;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.parameters.Parameter;
import io.swagger.v3.parser.core.models.ParseOptions;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringUtils;
import org.openapitools.codegen.CodegenConstants;
import org.openapitools.codegen.CodegenParameter;
import org.openapitools.codegen.DefaultGenerator;
import org.openapitools.codegen.config.CodegenConfigurator;
import org.openapitools.codegen.languages.AbstractPythonConnexionServerCodegen;
import org.openapitools.codegen.languages.PythonAiohttpConnexionServerCodegen;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;

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.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;

import static org.assertj.core.api.Assertions.assertThat;
import static org.openapitools.codegen.TestUtils.assertFileContains;
import static org.openapitools.codegen.TestUtils.assertFileExists;
import static org.openapitools.codegen.TestUtils.assertFileNotContains;
import static org.openapitools.codegen.languages.AbstractPythonConnexionServerCodegen.*;

public class AbstractPythonConnexionServerCodegenTest {
Expand Down Expand Up @@ -142,4 +160,176 @@ private static String platformAgnosticPath(String... nodes) {
return StringUtils.join(nodes, File.separatorChar);
}

@Test(description = "python-aiohttp map parameters should iterate .items()")
public void testAiohttpMapParametersIterateItems() throws IOException {
String specPath = "src/test/resources/3_1/python/map-and-freeform-params.yaml";

File aiohttpOutput = Files.createTempDirectory("test-aiohttp-map-params").toFile().getCanonicalFile();
try {
CodegenConfigurator aiohttpConfig = new CodegenConfigurator()
.setGeneratorName("python-aiohttp")
.setInputSpec(specPath)
.setOutputDir(aiohttpOutput.getAbsolutePath());
new DefaultGenerator().opts(aiohttpConfig.toClientOptInput()).generate();

Path aiohttpController = Paths.get(
aiohttpOutput.getAbsolutePath(),
"openapi_server",
"controllers",
"default_controller.py");
assertFileExists(aiohttpController);
assertFileContains(
aiohttpController,
" if date_map is not None:\n date_map = {k: util.deserialize_date(v) for k, v in date_map.items()}",
" if datetime_map is not None:\n datetime_map = {k: util.deserialize_datetime(v) for k, v in datetime_map.items()}",
" if model_map is not None:\n model_map = {k: Tag.from_dict(v) for k, v in model_map.items()}");
assertFileNotContains(
aiohttpController,
"for k, v in date_map}",
"for k, v in datetime_map}",
"for k, v in model_map}");
} finally {
FileUtils.deleteDirectory(aiohttpOutput);
}
}

@Test(description = "Connexion servers should skip from_dict only on untyped/free-form object parameters and keep from_dict on composed/model parameters")
public void testFreeFormAndAnyTypeParametersDoNotCallFromDict() throws IOException {
String specPath = "src/test/resources/3_1/python/map-and-freeform-params.yaml";
String composedSpecPath = "src/test/resources/3_1/python/composed-query-params.yaml";

OpenAPI openAPI = new OpenAPIParser().readLocation(specPath, null, new ParseOptions()).getOpenAPI();
PythonAiohttpConnexionServerCodegen codegen = new PythonAiohttpConnexionServerCodegen();
codegen.setOpenAPI(openAPI);
List<Parameter> testParameters = openAPI.getPaths().get("/test").getGet().getParameters();
CodegenParameter freeFormParam = codegen.fromParameter(testParameters.get(3), new HashSet<>());
CodegenParameter anyValParam = codegen.fromParameter(testParameters.get(4), new HashSet<>());

assertThat(freeFormParam.paramName).isEqualTo("free_form");
assertThat(freeFormParam.isFreeFormObject).isTrue();
assertThat(freeFormParam.isAnyType).isFalse();
assertThat(freeFormParam.isContainer).isFalse();
assertThat(freeFormParam.isPrimitiveType).isFalse();
assertThat(freeFormParam.dataType).isEqualTo("object");

assertThat(anyValParam.paramName).isEqualTo("any_val");
assertThat(anyValParam.isFreeFormObject).isFalse();
assertThat(anyValParam.isAnyType).isTrue();
assertThat(anyValParam.isContainer).isFalse();
assertThat(anyValParam.isPrimitiveType).isFalse();
assertThat(anyValParam.dataType).isEqualTo("object");

OpenAPI composedOpenAPI = new OpenAPIParser().readLocation(composedSpecPath, null, new ParseOptions()).getOpenAPI();
PythonAiohttpConnexionServerCodegen composedCodegen = new PythonAiohttpConnexionServerCodegen();
composedCodegen.setOpenAPI(composedOpenAPI);
List<Parameter> probeParameters = composedOpenAPI.getPaths().get("/t").getGet().getParameters();
CodegenParameter composedParam = composedCodegen.fromParameter(probeParameters.get(0), new HashSet<>());
CodegenParameter oneOfParam = composedCodegen.fromParameter(probeParameters.get(1), new HashSet<>());
CodegenParameter addlParam = composedCodegen.fromParameter(probeParameters.get(2), new HashSet<>());
assertThat(composedParam.isAnyType).isTrue();
assertThat(composedParam.dataType).isEqualTo("Composed");
assertThat(oneOfParam.isAnyType).isTrue();
assertThat(oneOfParam.dataType).isEqualTo("OneOfM");
assertThat(addlParam.paramName).isEqualTo("addl");
assertThat(addlParam.isMap).isTrue();
assertThat(addlParam.isContainer).isTrue();
assertThat(addlParam.isFreeFormObject).isFalse();
assertThat(addlParam.isAnyType).isFalse();
assertThat(addlParam.dataType).isEqualTo("WithAddl");

File aiohttpOutput = Files.createTempDirectory("test-aiohttp-freeform-params").toFile().getCanonicalFile();
try {
CodegenConfigurator aiohttpConfig = new CodegenConfigurator()
.setGeneratorName("python-aiohttp")
.setInputSpec(specPath)
.setOutputDir(aiohttpOutput.getAbsolutePath());
new DefaultGenerator().opts(aiohttpConfig.toClientOptInput()).generate();

Path aiohttpController = Paths.get(
aiohttpOutput.getAbsolutePath(),
"openapi_server",
"controllers",
"default_controller.py");
assertFileExists(aiohttpController);
assertFileNotContains(
aiohttpController,
"free_form = ",
"any_val = ");
} finally {
FileUtils.deleteDirectory(aiohttpOutput);
}

File blueplanetOutput = Files.createTempDirectory("test-blueplanet-freeform-params").toFile().getCanonicalFile();
try {
CodegenConfigurator blueplanetConfig = new CodegenConfigurator()
.setGeneratorName("python-blueplanet")
.setInputSpec(specPath)
.setOutputDir(blueplanetOutput.getAbsolutePath());
new DefaultGenerator().opts(blueplanetConfig.toClientOptInput()).generate();

Path blueplanetController = Paths.get(
blueplanetOutput.getAbsolutePath(),
"app",
"openapi_server",
"controllers",
"default_controller.py");
assertFileExists(blueplanetController);
assertFileNotContains(
blueplanetController,
"free_form = ",
"any_val = ");
} finally {
FileUtils.deleteDirectory(blueplanetOutput);
}

File flaskOutput = Files.createTempDirectory("test-flask-freeform-params").toFile().getCanonicalFile();
try {
CodegenConfigurator flaskConfig = new CodegenConfigurator()
.setGeneratorName("python-flask")
.setInputSpec(specPath)
.setOutputDir(flaskOutput.getAbsolutePath());
new DefaultGenerator().opts(flaskConfig.toClientOptInput()).generate();

Path flaskController = Paths.get(
flaskOutput.getAbsolutePath(),
"openapi_server",
"controllers",
"default_controller.py");
assertFileExists(flaskController);
assertFileNotContains(
flaskController,
"free_form =",
"any_val =");
} finally {
FileUtils.deleteDirectory(flaskOutput);
}

File flaskComposedOutput = Files.createTempDirectory("test-flask-composed-params").toFile().getCanonicalFile();
try {
CodegenConfigurator flaskComposedConfig = new CodegenConfigurator()
.setGeneratorName("python-flask")
.setInputSpec(composedSpecPath)
.setOutputDir(flaskComposedOutput.getAbsolutePath());
new DefaultGenerator().opts(flaskComposedConfig.toClientOptInput()).generate();

Path flaskComposedController = Paths.get(
flaskComposedOutput.getAbsolutePath(),
"openapi_server",
"controllers",
"default_controller.py");
assertFileExists(flaskComposedController);
assertFileContains(
flaskComposedController,
":type addl: Dict[str, str]",
"composed = Composed.from_dict(flask.request.get_json()) # noqa: E501",
"oneof = OneOfM.from_dict(flask.request.get_json()) # noqa: E501",
"plain = Pet.from_dict(flask.request.get_json()) # noqa: E501");
assertFileNotContains(
flaskComposedController,
"addl =");
} finally {
FileUtils.deleteDirectory(flaskComposedOutput);
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
openapi: 3.1.0

info:
title: Composed and model query parameter test
version: 1.0.0

paths:
/t:
get:
operationId: probe
parameters:
- name: composed
in: query
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/Composed'
- name: oneof
in: query
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/OneOfM'
- name: addl
Comment thread
cubic-dev-ai[bot] marked this conversation as resolved.
in: query
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/WithAddl'
- name: plain
in: query
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/Pet'
responses:
'200':
description: OK

components:
schemas:
Pet:
type: object
properties:
name:
type: string
Tag:
type: object
properties:
label:
type: string
Composed:
allOf:
- $ref: '#/components/schemas/Pet'
- $ref: '#/components/schemas/Tag'
OneOfM:
oneOf:
- $ref: '#/components/schemas/Pet'
- $ref: '#/components/schemas/Tag'
WithAddl:
type: object
properties:
a:
type: string
additionalProperties:
type: string
Loading
Loading