Skip to content
Open
Original file line number Diff line number Diff line change
Expand Up @@ -89,12 +89,17 @@ public void serialize(Boolean value, JsonGenerator gen, SerializerProvider seria
public static final String USE_PYTHON_SRC_ROOT_IN_IMPORTS = "usePythonSrcRootInImports";
public static final String MOVE_TESTS_UNDER_PYTHON_SRC_ROOT = "testsUsePythonSrcRoot";
static final String MEDIA_TYPE = "mediaType";
// names escaped for a single-quoted Python literal; emitting them via {{baseName}}/{{name}}
// would HTML-escape them (e.g. "a&b" -> "a&b")
static final String X_QUOTED_BASE_NAME = "x-python-connexion-quoted-base-name";
static final String X_QUOTED_KEY_PARAM_NAME = "x-python-connexion-quoted-key-param-name";
// Effective style/explode of an object-typed (model) query or header parameter.
static final String X_OBJECT_STYLE = "x-python-connexion-object-style";
static final String X_OBJECT_EXPLODE = "x-python-connexion-object-explode";
// Map<String, String> of wire values, used only by prepareObjectParameterExample (never by templates).
static final String X_OBJECT_EXAMPLE = "x-python-connexion-object-example";
// baseName escaped for a single-quoted Python literal.
// CodegenParameter.baseName escaped for a single-quoted Python literal; same name and
// meaning as the vendor extension used by the object-params work on controllers
static final String X_QUOTED_NAME = "x-python-connexion-quoted-name";
static final String X_TEST_UNSERIALIZABLE = "x-python-connexion-test-unserializable";
static final String HAS_OBJECT_PARAMS = "connexionObjectParams";
Expand Down Expand Up @@ -617,6 +622,12 @@ public List<CodegenSecurity> fromSecurity(Map<String, SecurityScheme> schemes) {
for (CodegenSecurity codegenSecurity : codegenSecurities) {
codegenSecurity.vendorExtensions.put("x-python-connexion-security-function-suffix",
securityFunctionNameSuffix(codegenSecurity.name));
if (codegenSecurity.keyParamName != null) {
// an apiKey-in-header's keyParamName goes into a single-quoted Python literal
// in generated tests (it is the header name, not the security scheme name)
codegenSecurity.vendorExtensions.put(X_QUOTED_KEY_PARAM_NAME,
escapeForPythonSingleQuotedLiteral(codegenSecurity.keyParamName));
}
}
return codegenSecurities;
}
Expand Down Expand Up @@ -813,9 +824,56 @@ public void postProcessModelProperty(CodegenModel model, CodegenProperty propert
if (anyPropertyMatches(property, p -> p.isUuid)) {
addImport(model, "from uuid import UUID");
}
// baseName goes into a single-quoted Python literal in attribute_map
property.vendorExtensions.put(X_QUOTED_BASE_NAME, escapeForPythonSingleQuotedLiteral(property.baseName));
postProcessPattern(property.pattern, property.vendorExtensions);
}

/**
* Escape a name for embedding inside a single-quoted Python string literal (the
* surrounding quotes are supplied by the templates). Unlike
* {@link DefaultCodegen#escapeTextInSingleQuotes}, which folds tab/newline/CR into
* spaces and rewrites "'''" via {@link AbstractPythonCodegen#escapeUnsafeCharacters},
* this conversion is lossless: the literal always evaluates back to the original name.
*/
public static String escapeForPythonSingleQuotedLiteral(String input) {
if (input == null) {
return null;
}
StringBuilder sb = new StringBuilder(input.length());
for (int i = 0; i < input.length(); i++) {
char c = input.charAt(i);
switch (c) {
case '\\':
sb.append("\\\\");
break;
case '\'':
sb.append("\\'");
break;
case '\n':
sb.append("\\n");
break;
case '\r':
sb.append("\\r");
break;
case '\t':
sb.append("\\t");
break;
default:
if (c < 0x20 || (c >= 0x7f && c <= 0x9f)) {
sb.append("\\x");
if (c < 0x10) {
sb.append('0');
}
sb.append(Integer.toHexString(c));
} else {
sb.append(c);
}
}
}
return sb.toString();
}

@Override
public Map<String, ModelsMap> postProcessAllModels(Map<String, ModelsMap> objs) {
Map<String, ModelsMap> result = super.postProcessAllModels(objs);
Expand Down Expand Up @@ -1041,6 +1099,12 @@ public void postProcessParameter(CodegenParameter parameter) {
if (openapiName instanceof String) {
parameter.baseName = (String) openapiName;
}
if (parameter.baseName != null) {
// baseName (the original wire name) goes into single-quoted Python literals in
// generated tests (query keys, blueplanet header names). Same extension name and
// meaning as in the object-params work that consumes it for controller lines.
parameter.vendorExtensions.put(X_QUOTED_NAME, escapeForPythonSingleQuotedLiteral(parameter.baseName));
}
if (parameter.isHeaderParam || parameter.isCookieParam) {
// Header and cookie parameters are not passed to the handler by
// Connexion; the handler body reads them from the request into a
Expand Down Expand Up @@ -1210,7 +1274,7 @@ protected void postProcessConnexionOperationMap(OperationMap operations, Map<Str
for (List<CodegenParameter> params : Arrays.asList(operation.allParams, operation.headerParams,
operation.cookieParams)) {
for (CodegenParameter param : params) {
param.vendorExtensions.put(X_QUOTED_NAME, escapeTextInSingleQuotes(param.baseName));
param.vendorExtensions.put(X_QUOTED_NAME, escapeForPythonSingleQuotedLiteral(param.baseName));
}
}
// allParams, queryParams and headerParams hold separate copies of each parameter.
Expand Down Expand Up @@ -1330,7 +1394,7 @@ protected void postProcessConnexionOperationMap(OperationMap operations, Map<Str
Map<String, String> entry = new HashMap<>();
Object openapiName = param.vendorExtensions.get("x-python-connexion-openapi-name");
String wireName = openapiName != null ? openapiName.toString() : param.baseName;
entry.put("name", escapeTextInSingleQuotes(wireName));
entry.put("name", escapeForPythonSingleQuotedLiteral(wireName));
Comment thread
cubic-dev-ai[bot] marked this conversation as resolved.
Resolved
entry.put("example", param.example);
testQueryString.add(entry);
}
Expand All @@ -1342,7 +1406,7 @@ protected void postProcessConnexionOperationMap(OperationMap operations, Map<Str
}
if (Boolean.TRUE.equals(auth.isKeyInQuery)) {
Map<String, String> entry = new HashMap<>();
entry.put("name", escapeTextInSingleQuotes(auth.keyParamName));
entry.put("name", escapeForPythonSingleQuotedLiteral(auth.keyParamName));
entry.put("example", "'special-key'");
testQueryString.add(entry);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ async def test_{{operationId}}(client):
'Accept': '{{{mediaType}}}',{{/vendorExtensions.x-preferred-produce}}{{#vendorExtensions.x-preferred-consume}}
'Content-Type': '{{{mediaType}}}',{{/vendorExtensions.x-preferred-consume}}{{#headerParams}}
'{{{vendorExtensions.x-python-connexion-quoted-name}}}': {{#vendorExtensions.x-python-connexion-test-header-value}}{{{vendorExtensions.x-python-connexion-test-header-value}}}{{/vendorExtensions.x-python-connexion-test-header-value}}{{^vendorExtensions.x-python-connexion-test-header-value}}{{{example}}}{{/vendorExtensions.x-python-connexion-test-header-value}},{{/headerParams}}{{#authMethods}}
{{#isOAuth}}'Authorization': 'Bearer special-key',{{/isOAuth}}{{#isApiKey}}{{#isKeyInHeader}}'{{keyParamName}}': 'special-key',{{/isKeyInHeader}}{{/isApiKey}}{{#isBasicBasic}}'Authorization': 'BasicZm9vOmJhcg==',{{/isBasicBasic}}{{#isBasicBearer}}'Authorization': 'Bearer special-key',{{/isBasicBearer}}{{/authMethods}}{{#vendorExtensions.x-test-apikey-cookie}}
{{#isOAuth}}'Authorization': 'Bearer special-key',{{/isOAuth}}{{#isApiKey}}{{#isKeyInHeader}}'{{{vendorExtensions.x-python-connexion-quoted-key-param-name}}}': 'special-key',{{/isKeyInHeader}}{{/isApiKey}}{{#isBasicBasic}}'Authorization': 'BasicZm9vOmJhcg==',{{/isBasicBasic}}{{#isBasicBearer}}'Authorization': 'Bearer special-key',{{/isBasicBearer}}{{/authMethods}}{{#vendorExtensions.x-test-apikey-cookie}}
'Cookie': '{{{vendorExtensions.x-test-apikey-cookie}}}',{{/vendorExtensions.x-test-apikey-cookie}}
}
{{#cookieParams}}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ class {{classname}}(Model):

self.attribute_map = {
{{#vars}}
'{{name}}': '{{baseName}}'{{^-last}},{{/-last}}
'{{name}}': '{{{vendorExtensions.x-python-connexion-quoted-base-name}}}'{{^-last}},{{/-last}}
{{/vars}}
}
{{#vars}}{{#-first}}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,10 +61,10 @@ def {{operationId}}({{#vendorExtensions.x-handler-params}}{{paramName}}{{^requir
"""
{{#allParams}}
{{#isHeaderParam}}
{{paramName}} = connexion.request.headers.get('{{baseName}}'{{#defaultValue}}, {{{defaultValue}}}{{/defaultValue}})
{{paramName}} = connexion.request.headers.get('{{{vendorExtensions.x-python-connexion-quoted-name}}}'{{#defaultValue}}, {{{defaultValue}}}{{/defaultValue}})
{{/isHeaderParam}}
{{#isCookieParam}}
{{paramName}} = connexion.request.cookies.get('{{baseName}}'{{#defaultValue}}, {{{defaultValue}}}{{/defaultValue}})
{{paramName}} = connexion.request.cookies.get('{{{vendorExtensions.x-python-connexion-quoted-name}}}'{{#defaultValue}}, {{{defaultValue}}}{{/defaultValue}})
{{/isCookieParam}}
{{#vendorExtensions.x-python-connexion-cast-int}}
if {{paramName}} is not None:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ class {{classname}}(Model):

self.attribute_map = {
{{#vars}}
'{{name}}': '{{baseName}}'{{^-last}},{{/-last}}
'{{name}}': '{{{vendorExtensions.x-python-connexion-quoted-base-name}}}'{{^-last}},{{/-last}}
{{/vars}}
}
{{#vars}}{{#-first}}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,13 @@ class {{#operations}}Test{{classname}}(BaseTestCase):
{{paramName}} = {{{example}}}
{{/bodyParam}}
{{#queryParams}}
{{#-first}}query_string = [{{/-first}}{{^-first}} {{/-first}}('{{paramName}}', {{{example}}}){{^-last}},{{/-last}}{{#-last}}]{{/-last}}
{{#-first}}query_string = [{{/-first}}{{^-first}} {{/-first}}('{{{vendorExtensions.x-python-connexion-quoted-name}}}', {{{example}}}){{^-last}},{{/-last}}{{#-last}}]{{/-last}}
{{/queryParams}}
{{#headerParams}}
{{#-first}}headers = [{{/-first}}{{^-first}} {{/-first}}('{{baseName}}', {{#vendorExtensions.x-python-connexion-test-header-value}}{{{vendorExtensions.x-python-connexion-test-header-value}}}{{/vendorExtensions.x-python-connexion-test-header-value}}{{^vendorExtensions.x-python-connexion-test-header-value}}{{{example}}}{{/vendorExtensions.x-python-connexion-test-header-value}}){{^-last}},{{/-last}}{{#-last}}]{{/-last}}
{{#-first}}headers = [{{/-first}}{{^-first}} {{/-first}}('{{{vendorExtensions.x-python-connexion-quoted-name}}}', {{#vendorExtensions.x-python-connexion-test-header-value}}{{{vendorExtensions.x-python-connexion-test-header-value}}}{{/vendorExtensions.x-python-connexion-test-header-value}}{{^vendorExtensions.x-python-connexion-test-header-value}}{{{example}}}{{/vendorExtensions.x-python-connexion-test-header-value}}){{^-last}},{{/-last}}{{#-last}}]{{/-last}}
{{/headerParams}}
{{#cookieParams}}{{#-first}}{{^headerParams}}headers = []
{{/headerParams}}headers += [('Cookie', '; '.join([{{/-first}}'{{baseName}}=' + {{#vendorExtensions.x-python-connexion-test-header-value}}{{{vendorExtensions.x-python-connexion-test-header-value}}}{{/vendorExtensions.x-python-connexion-test-header-value}}{{^vendorExtensions.x-python-connexion-test-header-value}}str({{{example}}}){{/vendorExtensions.x-python-connexion-test-header-value}}{{^-last}}, {{/-last}}{{#-last}}]))]{{/-last}}
{{/headerParams}}headers += [('Cookie', '; '.join([{{/-first}}'{{{vendorExtensions.x-python-connexion-quoted-name}}}=' + {{#vendorExtensions.x-python-connexion-test-header-value}}{{{vendorExtensions.x-python-connexion-test-header-value}}}{{/vendorExtensions.x-python-connexion-test-header-value}}{{^vendorExtensions.x-python-connexion-test-header-value}}str({{{example}}}){{/vendorExtensions.x-python-connexion-test-header-value}}{{^-last}}, {{/-last}}{{#-last}}]))]{{/-last}}
{{/cookieParams}}
{{#formParams}}
{{#-first}}data = dict({{/-first}}{{^-first}} {{/-first}}{{paramName}}={{{example}}}{{^-last}},{{/-last}}{{#-last}}){{/-last}}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ class {{#operations}}Test{{classname}}(BaseTestCase):
'Accept': '{{{mediaType}}}',{{/vendorExtensions.x-preferred-produce}}{{#vendorExtensions.x-preferred-consume}}
'Content-Type': '{{{mediaType}}}',{{/vendorExtensions.x-preferred-consume}}{{#headerParams}}
'{{{vendorExtensions.x-python-connexion-quoted-name}}}': {{#vendorExtensions.x-python-connexion-test-header-value}}{{{vendorExtensions.x-python-connexion-test-header-value}}}{{/vendorExtensions.x-python-connexion-test-header-value}}{{^vendorExtensions.x-python-connexion-test-header-value}}{{{example}}}{{/vendorExtensions.x-python-connexion-test-header-value}},{{/headerParams}}{{#authMethods}}
{{#isOAuth}}'Authorization': 'Bearer special-key',{{/isOAuth}}{{#isApiKey}}{{#isKeyInHeader}}'{{keyParamName}}': 'special-key',{{/isKeyInHeader}}{{/isApiKey}}{{#isBasicBasic}}'Authorization': 'Basic Zm9vOmJhcg==',{{/isBasicBasic}}{{#isBasicBearer}}'Authorization': 'Bearer special-key',{{/isBasicBearer}}{{/authMethods}}{{#vendorExtensions.x-test-apikey-cookie}}
{{#isOAuth}}'Authorization': 'Bearer special-key',{{/isOAuth}}{{#isApiKey}}{{#isKeyInHeader}}'{{{vendorExtensions.x-python-connexion-quoted-key-param-name}}}': 'special-key',{{/isKeyInHeader}}{{/isApiKey}}{{#isBasicBasic}}'Authorization': 'Basic Zm9vOmJhcg==',{{/isBasicBasic}}{{#isBasicBearer}}'Authorization': 'Bearer special-key',{{/isBasicBearer}}{{/authMethods}}{{#vendorExtensions.x-test-apikey-cookie}}
'Cookie': '{{{vendorExtensions.x-test-apikey-cookie}}}',{{/vendorExtensions.x-test-apikey-cookie}}
}
{{#cookieParams}}{{#-first}}headers['Cookie'] = '; '.join([{{/-first}}'{{{vendorExtensions.x-python-connexion-quoted-name}}}=' + {{#vendorExtensions.x-python-connexion-test-header-value}}{{{vendorExtensions.x-python-connexion-test-header-value}}}{{/vendorExtensions.x-python-connexion-test-header-value}}{{^vendorExtensions.x-python-connexion-test-header-value}}str({{{example}}}){{/vendorExtensions.x-python-connexion-test-header-value}}{{^-last}}, {{/-last}}{{#-last}}]){{/-last}}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ class {{classname}}(Model):

self.attribute_map = {
{{#vars}}
'{{name}}': '{{baseName}}'{{^-last}},{{/-last}}
'{{name}}': '{{{vendorExtensions.x-python-connexion-quoted-base-name}}}'{{^-last}},{{/-last}}
{{/vars}}
}
{{#vars}}{{#-first}}
Expand Down
Loading
Loading