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 @@ -655,17 +655,143 @@ private List<Map<String, String>> toPyImports(CodegenModel cm, Set<String> impor
return pyImports;
}

@Override
public String toDefaultValue(Schema p) {
Schema<?> unaliased = p != null ? ModelUtils.unaliasSchema(this.openAPI, p) : null;
Object rawDefault = p != null && p.getDefault() != null ? p.getDefault()
: (unaliased != null ? unaliased.getDefault() : null);
Schema<?> target = unaliased != null ? unaliased : p;
if (target != null && ModelUtils.isArraySchema(target) && rawDefault instanceof Iterable<?>) {
return formatPythonDefaultValue(rawDefault, target);
}
return super.toDefaultValue(p);
}

private String formatPythonDefaultValue(Object value, Schema<?> schema) {
Schema<?> target = schema != null ? ModelUtils.unaliasSchema(this.openAPI, schema) : null;
if (value == null || (value instanceof com.fasterxml.jackson.databind.JsonNode
&& ((com.fasterxml.jackson.databind.JsonNode) value).isNull())) {
return "None";
}
if (value instanceof com.fasterxml.jackson.databind.JsonNode) {
com.fasterxml.jackson.databind.JsonNode node = (com.fasterxml.jackson.databind.JsonNode) value;
if (node.isArray()) {
Schema<?> itemSchema = target != null ? ModelUtils.getSchemaItems(target) : null;
List<String> elements = new ArrayList<>();
for (com.fasterxml.jackson.databind.JsonNode elem : node) {
elements.add(formatPythonDefaultValue(elem, itemSchema));
}
return "[" + String.join(", ", elements) + "]";
}
if (node.isObject()) {
List<String> entries = new ArrayList<>();
Iterator<Map.Entry<String, com.fasterxml.jackson.databind.JsonNode>> fields = node.fields();
while (fields.hasNext()) {
Map.Entry<String, com.fasterxml.jackson.databind.JsonNode> entry = fields.next();
Schema<?> propSchema = null;
if (target != null && target.getProperties() != null) {
propSchema = (Schema<?>) target.getProperties().get(entry.getKey());
} else if (target != null && ModelUtils.getAdditionalProperties(target) != null) {
propSchema = ModelUtils.getAdditionalProperties(target);
}
entries.add(formatPythonStringLiteral(entry.getKey()) + ": "
+ formatPythonDefaultValue(entry.getValue(), propSchema));
}
return "{" + String.join(", ", entries) + "}";
}
if (node.isBoolean() || (target != null && ModelUtils.isBooleanSchema(target))) {
return node.asBoolean() ? "True" : "False";
}
if (node.isNumber() || (target != null && (ModelUtils.isIntegerSchema(target) || ModelUtils.isNumberSchema(target)))) {
return node.asText();
}
return formatPythonStringLiteral(node.asText());
}
if (value instanceof Iterable<?>) {
Schema<?> itemSchema = target != null ? ModelUtils.getSchemaItems(target) : null;
List<String> elements = new ArrayList<>();
for (Object elem : (Iterable<?>) value) {
elements.add(formatPythonDefaultValue(elem, itemSchema));
}
return "[" + String.join(", ", elements) + "]";
}
if (value instanceof Map<?, ?>) {
List<String> entries = new ArrayList<>();
for (Map.Entry<?, ?> entry : ((Map<?, ?>) value).entrySet()) {
String key = String.valueOf(entry.getKey());
Schema<?> propSchema = null;
if (target != null && target.getProperties() != null) {
propSchema = (Schema<?>) target.getProperties().get(key);
} else if (target != null && ModelUtils.getAdditionalProperties(target) != null) {
propSchema = ModelUtils.getAdditionalProperties(target);
}
entries.add(formatPythonStringLiteral(key) + ": "
+ formatPythonDefaultValue(entry.getValue(), propSchema));
}
return "{" + String.join(", ", entries) + "}";
}
if (value instanceof Boolean || (target != null && ModelUtils.isBooleanSchema(target))) {
return Boolean.parseBoolean(value.toString()) ? "True" : "False";
}
if (value instanceof Number || (target != null && (ModelUtils.isIntegerSchema(target) || ModelUtils.isNumberSchema(target)))) {
return value.toString();
}
return formatPythonStringLiteral(value.toString());
}

@Override
public void postProcessParameter(CodegenParameter parameter) {
postProcessPattern(parameter.pattern, parameter.vendorExtensions);
Object openapiName = parameter.vendorExtensions.get("x-python-connexion-openapi-name");
if (openapiName instanceof String) {
parameter.baseName = (String) openapiName;
}
if (parameter.isHeaderParam || parameter.isCookieParam) {
boolean isJsonContent = parameter.headerIsJsonMimeType
|| isJsonMimeType(parameter.contentType)
|| isJsonVendorMimeType(parameter.contentType);
if (isJsonContent) {
parameter.vendorExtensions.put("x-python-connexion-json-content", Boolean.TRUE);
} else if (parameter.isArray) {
if (parameter.items == null || !parameter.items.isModel) {
parameter.vendorExtensions.put("x-python-connexion-split-csv", Boolean.TRUE);
}
if (parameter.items != null) {
if (parameter.items.isInteger || parameter.items.isLong) {
parameter.vendorExtensions.put("x-python-connexion-cast-int-items", Boolean.TRUE);
} else if (parameter.items.isNumber || parameter.items.isFloat || parameter.items.isDouble) {
parameter.vendorExtensions.put("x-python-connexion-cast-float-items", Boolean.TRUE);
} else if (parameter.items.isBoolean) {
parameter.vendorExtensions.put("x-python-connexion-cast-bool-items", Boolean.TRUE);
}
}
} else if (parameter.isInteger || parameter.isLong) {
parameter.vendorExtensions.put("x-python-connexion-cast-int", Boolean.TRUE);
} else if (parameter.isNumber || parameter.isFloat || parameter.isDouble) {
parameter.vendorExtensions.put("x-python-connexion-cast-float", Boolean.TRUE);
} else if (parameter.isBoolean) {
parameter.vendorExtensions.put("x-python-connexion-cast-bool", Boolean.TRUE);
}
}
}

@Override
public OperationsMap postProcessOperationsWithModels(OperationsMap objs, List<ModelMap> allModels) {
OperationMap operations = objs.getOperations();
List<CodegenOperation> operationList = operations.getOperation();

boolean hasJsonHeaderParam = false;
for (CodegenOperation operation : operationList) {
List<CodegenParameter> handlerParams = new ArrayList<>();
for (CodegenParameter param : operation.allParams) {
if (!param.isHeaderParam && !param.isCookieParam) {
handlerParams.add(param);
}
if (Boolean.TRUE.equals(param.vendorExtensions.get("x-python-connexion-json-content"))) {
hasJsonHeaderParam = true;
}
}
operation.vendorExtensions.put("x-handler-params", handlerParams);
Map<String, String> skipTests = new HashMap<>();
// Set flag to deactivate tests due to connexion issue.
if (operation.consumes != null) {
Expand Down Expand Up @@ -725,6 +851,9 @@ public OperationsMap postProcessOperationsWithModels(OperationsMap objs, List<Mo
}
}
}
if (hasJsonHeaderParam) {
objs.put("hasJsonHeaderParam", Boolean.TRUE);
}
return objs;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
{{#hasJsonHeaderParam}}
import json
{{/hasJsonHeaderParam}}
from typing import List, Dict
from aiohttp import web

Expand All @@ -8,7 +11,7 @@ from {{packageName}} import util
{{#operation}}


async def {{operationId}}(request: web.Request, {{#allParams}}{{paramName}}{{^required}}=None{{/required}}{{^-last}}, {{/-last}}{{/allParams}}) -> web.Response:
async def {{operationId}}(request: web.Request, {{#vendorExtensions.x-handler-params}}{{paramName}}{{^required}}=None{{/required}}{{^-last}}, {{/-last}}{{/vendorExtensions.x-handler-params}}) -> web.Response:
"""{{summary}}{{^summary}}{{operationId}}{{/summary}}

{{notes}}
Expand Down Expand Up @@ -57,31 +60,95 @@ async def {{operationId}}(request: web.Request, {{#allParams}}{{paramName}}{{^re

"""
{{#allParams}}
{{#isHeaderParam}}
{{paramName}} = request.headers.get('{{baseName}}'{{#defaultValue}}, {{{defaultValue}}}{{/defaultValue}})

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Header/cookie lookups leave non-JSON complex array parameters as raw strings, so valid simple-style arrays of model items never become List[Model]. Parse the parameter’s wire representation before the model-item conversion, or retain Connexion’s converted value.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At modules/openapi-generator/src/main/resources/python-aiohttp/controller.mustache, line 64:

<comment>Header/cookie lookups leave non-JSON complex array parameters as raw strings, so valid simple-style arrays of model items never become `List[Model]`. Parse the parameter’s wire representation before the model-item conversion, or retain Connexion’s converted value.</comment>

<file context>
@@ -57,6 +60,44 @@ async def {{operationId}}(request: web.Request, {{#allParams}}{{paramName}}{{^re
     """
     {{#allParams}}
+        {{#isHeaderParam}}
+    {{paramName}} = request.headers.get('{{baseName}}'{{#defaultValue}}, {{{defaultValue}}}{{/defaultValue}})
+        {{/isHeaderParam}}
+        {{#isCookieParam}}
</file context>

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Partially addressed in cf71763. Arrays whose items reference an enum or a primitive alias (which also have a complexType but aren't models) are now split on , like other primitive arrays before conversion.

For arrays of object models I've intentionally left the raw string alone. OpenAPI's parameter serialization (style: simple for headers, style: form for cookies) defines primitives, arrays of primitives and a single flat object, but no wire format for an array of objects; those need content: application/json, which is already decoded with json.loads before from_dict. Splitting such a value on , would produce List[str], and Model.from_dict(str) returns an empty model, which silently discards the data. Happy to change this if you'd prefer a different behavior.

Comment thread
cubic-dev-ai[bot] marked this conversation as resolved.
{{/isHeaderParam}}
{{#isCookieParam}}
{{paramName}} = request.cookies.get('{{baseName}}'{{#defaultValue}}, {{{defaultValue}}}{{/defaultValue}})
{{/isCookieParam}}
{{#vendorExtensions.x-python-connexion-cast-int}}
if {{paramName}} is not None:
{{paramName}} = int({{paramName}})
{{/vendorExtensions.x-python-connexion-cast-int}}
{{#vendorExtensions.x-python-connexion-cast-float}}
if {{paramName}} is not None:
{{paramName}} = float({{paramName}})
{{/vendorExtensions.x-python-connexion-cast-float}}
{{#vendorExtensions.x-python-connexion-cast-bool}}
if isinstance({{paramName}}, str):
{{paramName}} = {{paramName}}.lower() == 'true'
{{/vendorExtensions.x-python-connexion-cast-bool}}
{{#vendorExtensions.x-python-connexion-json-content}}
if isinstance({{paramName}}, str):
{{paramName}} = json.loads({{paramName}})
{{/vendorExtensions.x-python-connexion-json-content}}
{{#vendorExtensions.x-python-connexion-split-csv}}
if isinstance({{paramName}}, str):
{{paramName}} = {{paramName}}.split(',')
{{/vendorExtensions.x-python-connexion-split-csv}}
{{#vendorExtensions.x-python-connexion-cast-int-items}}
if isinstance({{paramName}}, list):
{{paramName}} = [int(x) for x in {{paramName}}]
{{/vendorExtensions.x-python-connexion-cast-int-items}}
{{#vendorExtensions.x-python-connexion-cast-float-items}}
if isinstance({{paramName}}, list):
{{paramName}} = [float(x) for x in {{paramName}}]
{{/vendorExtensions.x-python-connexion-cast-float-items}}
{{#vendorExtensions.x-python-connexion-cast-bool-items}}
if isinstance({{paramName}}, list):
{{paramName}} = [x.lower() == 'true' if isinstance(x, str) else bool(x) for x in {{paramName}}]
{{/vendorExtensions.x-python-connexion-cast-bool-items}}
{{^isContainer}}
{{#isDate}}
{{paramName}} = util.deserialize_date({{paramName}})
if {{paramName}} is not None:
{{paramName}} = util.deserialize_date({{paramName}})
{{/isDate}}
{{#isDateTime}}
{{paramName}} = util.deserialize_datetime({{paramName}})
if {{paramName}} is not None:
{{paramName}} = util.deserialize_datetime({{paramName}})
{{/isDateTime}}
{{^isPrimitiveType}}
{{^isFile}}
{{^isUuid}}
{{#isBodyParam}}
{{paramName}} = {{baseType}}.from_dict({{paramName}})
{{/isBodyParam}}
{{^isBodyParam}}
if isinstance({{paramName}}, dict):
{{paramName}} = {{baseType}}{{^baseType}}{{#dataType}}{{.}}{{/dataType}}{{/baseType}}.from_dict({{paramName}})
{{/isBodyParam}}
{{/isUuid}}
{{/isFile}}
{{/isPrimitiveType}}
{{/isContainer}}
{{#isArray}}
{{#items}}
{{#isDate}}
{{#isBodyParam}}
{{paramName}} = [util.deserialize_date(s) for s in {{paramName}}]
{{/isBodyParam}}
{{^isBodyParam}}
if isinstance({{paramName}}, list):
{{paramName}} = [util.deserialize_date(s) for s in {{paramName}}]
{{/isBodyParam}}
{{/isDate}}
{{#isDateTime}}
{{#isBodyParam}}
{{paramName}} = [util.deserialize_datetime(s) for s in {{paramName}}]
{{/isBodyParam}}
{{^isBodyParam}}
if isinstance({{paramName}}, list):
{{paramName}} = [util.deserialize_datetime(s) for s in {{paramName}}]
{{/isBodyParam}}
{{/isDateTime}}
{{#complexType}}
{{#isBodyParam}}
{{paramName}} = [{{complexType}}.from_dict(d) for d in {{paramName}}]
{{/isBodyParam}}
{{^isBodyParam}}
if isinstance({{paramName}}, list):
{{paramName}} = [{{complexType}}.from_dict(d) for d in {{paramName}}]
{{/isBodyParam}}
{{/complexType}}
{{/items}}
{{/isArray}}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ async def test_{{operationId}}(client):
headers = { {{#vendorExtensions.x-preferred-produce}}
'Accept': '{{{mediaType}}}',{{/vendorExtensions.x-preferred-produce}}{{#vendorExtensions.x-preferred-consume}}
'Content-Type': '{{{mediaType}}}',{{/vendorExtensions.x-preferred-consume}}{{#headerParams}}
'{{paramName}}': {{{example}}},{{/headerParams}}{{#authMethods}}
'{{baseName}}': {{{example}}},{{/headerParams}}{{#authMethods}}
{{#isOAuth}}'Authorization': 'Bearer special-key',{{/isOAuth}}{{#isApiKey}}'{{name}}': 'special-key',{{/isApiKey}}{{#isBasicBasic}}'Authorization': 'BasicZm9vOmJhcg==',{{/isBasicBasic}}{{#isBasicBearer}}'Authorization': 'Bearer special-key',{{/isBasicBearer}}{{/authMethods}}
}
{{#formParams}}
Expand Down
Loading
Loading