Skip to content
Draft
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 @@ -829,6 +829,63 @@ public GeneratorLanguage generatorLanguage() {
return GeneratorLanguage.PYTHON;
}

private String toPythonType(String openApiType) {
if (openApiType == null) {
return "Any";
}
if (openApiType.startsWith("array[")) {
String innerType = openApiType.substring(6, openApiType.length() - 1);
return "List[" + toPythonType(innerType) + "]";
}
if (openApiType.startsWith("map[")) {
String innerType = openApiType.substring(4, openApiType.length() - 1);
return "Dict[" + innerType + "]";
}
switch (openApiType) {
case "string":
return "str";
case "array":
return "List";
case "object":
return "Dict";
case "number":
return "float";
case "integer":
return "int";
case "boolean":
return "bool";
case "binary":
return "bytes";
default:
return openApiType;
}
}

/**
* Post-process the codegen models to convert OpenAPI types to Python types in anyOf/oneOf.
*/
@Override
public ModelsMap postProcessModels(ModelsMap objs) {
for (ModelMap mo : objs.getModels()) {
CodegenModel model = mo.getModel();
if (model.anyOf != null && !model.anyOf.isEmpty()) {
Set<String> pythonTypes = new LinkedHashSet<>();
for (String type : model.anyOf) {
pythonTypes.add(toPythonType(type));
}
model.anyOf = pythonTypes;
}
if (model.oneOf != null && !model.oneOf.isEmpty()) {
Set<String> pythonTypes = new LinkedHashSet<>();
for (String type : model.oneOf) {
pythonTypes.add(toPythonType(type));
}
model.oneOf = pythonTypes;
}
}
return postProcessModelsEnum(objs);
}

@Override
public Map<String, ModelsMap> postProcessAllModels(Map<String, ModelsMap> objs) {
final Map<String, ModelsMap> processed = super.postProcessAllModels(objs);
Expand Down Expand Up @@ -1389,12 +1446,6 @@ public String addRegularExpressionDelimiter(String pattern) {
return pattern;
}

@Override
public ModelsMap postProcessModels(ModelsMap objs) {
// process enum in models
return postProcessModelsEnum(objs);
}

@Override
public String toEnumVarName(String name, String datatype) {
if ("int".equals(datatype) || "float".equals(datatype)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1937,10 +1937,60 @@ public String addRegularExpressionDelimiter(String pattern) {
return pattern;
}

private String toPythonType(String openApiType) {
if (openApiType == null) {
return "Any";
}
if (openApiType.startsWith("array[")) {
String innerType = openApiType.substring(6, openApiType.length() - 1);
return "List[" + toPythonType(innerType) + "]";
}
if (openApiType.startsWith("map[")) {
String innerType = openApiType.substring(4, openApiType.length() - 1);
return "Dict[" + innerType + "]";
}
switch (openApiType) {
case "string":
return "str";
case "array":
return "List";
case "object":
return "Dict";
case "number":
return "float";
case "integer":
return "int";
case "boolean":
return "bool";
case "binary":
return "bytes";
default:
return openApiType;
}
}

@Override
public ModelsMap postProcessModels(ModelsMap objs) {
// process enum in models
return postProcessModelsEnum(objs);
ModelsMap processed = postProcessModelsEnum(objs);
for (ModelMap modelMap : processed.getModels()) {
CodegenModel model = modelMap.getModel();
if (model.anyOf != null && !model.anyOf.isEmpty()) {
Set<String> pythonTypes = new LinkedHashSet<>();
for (String type : model.anyOf) {
pythonTypes.add(toPythonType(type));
}
model.anyOf = pythonTypes;
}
if (model.oneOf != null && !model.oneOf.isEmpty()) {
Set<String> pythonTypes = new LinkedHashSet<>();
for (String type : model.oneOf) {
pythonTypes.add(toPythonType(type));
}
model.oneOf = pythonTypes;
}
}
return processed;
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -685,4 +685,30 @@ public void testNonPoetry1LicenseFormat() throws IOException {
// Verify it does NOT use the legacy string format
TestUtils.assertFileNotContains(pyprojectPath, "license = \"BSD-3-Clause\"");
}

@Test(description = "Test anyOf with string and array types - issue #17754")
public void testAnyOfStringArrayTypes() throws IOException {
File output = Files.createTempDirectory("test").toFile().getCanonicalFile();
output.deleteOnExit();

final CodegenConfigurator configurator = new CodegenConfigurator()
.setGeneratorName("python")
.setInputSpec("src/test/resources/3_0/issue_17754_anyof_string_array.yaml")
.setOutputDir(output.getAbsolutePath());

DefaultGenerator generator = new DefaultGenerator();
List<File> files = generator.opts(configurator.toClientOptInput()).generate();
files.forEach(File::deleteOnExit);

// The anyOf creates an inline model - check that model
Path valueModelPath = Paths.get(output.getAbsolutePath(), "openapi_client", "models", "key_value_pair_value.py");
TestUtils.assertFileExists(valueModelPath);

String content = Files.readAllLines(valueModelPath).stream().collect(Collectors.joining("\n"));

// The anyOf schemas should contain Python types (str, List[str]) not OpenAPI types (string, array)
// Check that ANY_OF_SCHEMAS contains "List[str]" and "str"
Assert.assertTrue(content.contains("KEYVALUEPAIRVALUE_ANY_OF_SCHEMAS = [\"List[str]\", \"str\"]"),
"Expected anyOf schemas to contain Python types: List[str], str");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
openapi: 3.1.0
info:
title: Test anyOf with string and array
version: 1.0.0
paths:
/:
get:
responses:
'200':
description: Request successful
content:
application/json:
schema:
$ref: '#/components/schemas/KeyValuePair'
components:
schemas:
KeyValuePair:
properties:
key:
type: string
value:
anyOf:
- items:
type: string
type: array
- type: string
type: object