Skip to content

Commit 60c27be

Browse files
authored
Fix Python (e.g. FastApi) example generation (#23537)
* Fix FastAPI example generation * Address reviewer feedback: check plural examples in getSchemaExample - Fall back to first entry of `examples` array when `example` is absent, fixing schema example extraction for OAS 3.1 specs. - Add unit tests for plural and singular example precedence via TestableFastAPICodegen helper subclass. - Add nickname property with examples array to test fixture. * fix(python-fastapi): keep trailing comma on same line as parameter Remove trailing newline from endpoint_argument_definition.mustache so the comma after each parameter stays inline rather than appearing on a new line. Regenerate python-fastapi petstore sample. * Update samples
1 parent 1e60f43 commit 60c27be

26 files changed

Lines changed: 2634 additions & 96 deletions

File tree

modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractPythonCodegen.java

Lines changed: 93 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package org.openapitools.codegen.languages;
1818

1919
import com.github.curiousoddman.rgxgen.RgxGen;
20+
import io.swagger.v3.core.util.Json;
2021
import io.swagger.v3.oas.models.examples.Example;
2122
import io.swagger.v3.oas.models.media.Schema;
2223
import io.swagger.v3.oas.models.parameters.Parameter;
@@ -635,18 +636,106 @@ public void setParameterExampleValue(CodegenParameter codegenParameter, Paramete
635636

636637
if (parameter.getExample() != null) {
637638
codegenParameter.example = parameter.getExample().toString();
639+
codegenParameter.vendorExtensions.put("x-py-example", toPythonLiteral(parameter.getExample()));
638640
} else if (parameter.getExamples() != null && !parameter.getExamples().isEmpty()) {
639641
Example example = parameter.getExamples().values().iterator().next();
640642
if (example.getValue() != null) {
641643
codegenParameter.example = example.getValue().toString();
644+
codegenParameter.vendorExtensions.put("x-py-example", toPythonLiteral(example.getValue()));
642645
}
643646
} else if (schema != null && schema.getExample() != null) {
644647
codegenParameter.example = schema.getExample().toString();
648+
codegenParameter.vendorExtensions.put("x-py-example", toPythonLiteral(schema.getExample()));
645649
}
646650

647651
setParameterExampleValue(codegenParameter);
648652
}
649653

654+
protected String toPythonExample(CodegenProperty cp) {
655+
if (cp == null) {
656+
return null;
657+
}
658+
659+
Object example = getSchemaExample(cp.jsonSchema);
660+
if (example != null) {
661+
return toPythonLiteral(example);
662+
}
663+
664+
return null;
665+
}
666+
667+
protected String toPythonExample(CodegenParameter cp) {
668+
if (cp == null) {
669+
return null;
670+
}
671+
672+
Object example = getSchemaExample(cp.jsonSchema);
673+
if (example != null) {
674+
return toPythonLiteral(example);
675+
}
676+
677+
return null;
678+
}
679+
680+
private Object getSchemaExample(String jsonSchema) {
681+
if (StringUtils.isEmpty(jsonSchema)) {
682+
return null;
683+
}
684+
try {
685+
Map<String, Object> schema = Json.mapper().readValue(jsonSchema, Map.class);
686+
Object example = schema.get("example");
687+
if (example != null) {
688+
return example;
689+
}
690+
Object examples = schema.get("examples");
691+
if (examples instanceof List && !((List<?>) examples).isEmpty()) {
692+
return ((List<?>) examples).get(0);
693+
}
694+
return null;
695+
} catch (Exception e) {
696+
return null;
697+
}
698+
}
699+
700+
protected String toPythonLiteral(Object value) {
701+
if (value == null) {
702+
return "None";
703+
}
704+
if (value instanceof String) {
705+
return toPythonStringLiteral((String) value);
706+
}
707+
if (value instanceof Boolean) {
708+
return (Boolean) value ? "True" : "False";
709+
}
710+
if (value instanceof Number) {
711+
return value.toString();
712+
}
713+
if (value instanceof Map) {
714+
List<String> entries = new ArrayList<>();
715+
for (Map.Entry<?, ?> entry : ((Map<?, ?>) value).entrySet()) {
716+
entries.add(toPythonStringLiteral(String.valueOf(entry.getKey())) + ": " + toPythonLiteral(entry.getValue()));
717+
}
718+
return "{" + StringUtils.join(entries, ", ") + "}";
719+
}
720+
if (value instanceof Iterable) {
721+
List<String> items = new ArrayList<>();
722+
for (Object item : (Iterable<?>) value) {
723+
items.add(toPythonLiteral(item));
724+
}
725+
return "[" + StringUtils.join(items, ", ") + "]";
726+
}
727+
728+
return toPythonStringLiteral(String.valueOf(value));
729+
}
730+
731+
protected String toPythonStringLiteral(String value) {
732+
try {
733+
return Json.mapper().writeValueAsString(value);
734+
} catch (Exception e) {
735+
return "\"" + escapeUnsafeCharacters(value) + "\"";
736+
}
737+
}
738+
650739
@Override
651740
public String sanitizeTag(String tag) {
652741
return sanitizeName(tag);
@@ -2171,10 +2260,10 @@ private String finalizeType(CodegenProperty cp, PythonType pt) {
21712260
pt.annotate("alias", cp.baseName);
21722261
}
21732262

2174-
/* TODO review as example may break the build
2175-
if (!StringUtils.isEmpty(cp.getExample())) { // has example
2176-
fields.add(String.format(Locale.ROOT, "example=%s", cp.getExample()));
2177-
}*/
2263+
String example = toPythonExample(cp);
2264+
if (example != null) {
2265+
pt.annotate("json_schema_extra", "{\"examples\": [" + example + "]}", false);
2266+
}
21782267

21792268
//String defaultValue = null;
21802269
if (!cp.required) { //optional
@@ -2247,11 +2336,6 @@ private String finalizeType(CodegenParameter cp, PythonType pt) {
22472336
pt.annotate("description", cp.description);
22482337
}
22492338

2250-
/* TODO support example
2251-
if (!StringUtils.isEmpty(cp.getExample())) { // has example
2252-
fields.add(String.format(Locale.ROOT, "example=%s", cp.getExample()));
2253-
}*/
2254-
22552339
//return pt.asTypeConstraint(moduleImports);
22562340
return pt.asTypeConstraintWithAnnotations(moduleImports);
22572341
}

0 commit comments

Comments
 (0)