Skip to content

Commit fa242fc

Browse files
committed
Merge branch 'master' of https://github.com/ranjanp75/openapi-generator into ranjanp75-master
2 parents ddb15d4 + 17a184c commit fa242fc

File tree

5 files changed

+190
-0
lines changed

5 files changed

+190
-0
lines changed

docs/generators/python.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ These options may be applied as additional-properties (cli) or configOptions (pl
2222
|dateFormat|date format for query parameters| |%Y-%m-%d|
2323
|datetimeFormat|datetime format for query parameters| |%Y-%m-%dT%H:%M:%S%z|
2424
|disallowAdditionalPropertiesIfNotPresent|If false, the 'additionalProperties' implementation (set to true by default) is compliant with the OAS and JSON schema specifications. If true (default), keep the old (incorrect) behaviour that 'additionalProperties' is set to false by default.|<dl><dt>**false**</dt><dd>The 'additionalProperties' implementation is compliant with the OAS and JSON schema specifications.</dd><dt>**true**</dt><dd>Keep the old (incorrect) behaviour that 'additionalProperties' is set to false by default.</dd></dl>|true|
25+
|enumUnknownDefaultCase|If the server adds new enum cases, that are unknown by an old spec/client, the client will fail to parse the network response.With this option enabled, each enum will have a new case, 'unknown_default_open_api', so that when the server sends an enum case that is not known by the client/spec, they can safely fallback to this case.| |false|
2526
|generateSourceCodeOnly|Specifies that only a library source code is to be generated.| |false|
2627
|hideGenerationTimestamp|Hides the generation timestamp when files are generated.| |true|
2728
|lazyImports|Enable lazy imports.| |false|

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,7 @@ public PythonClientCodegen() {
153153
cliOptions.add(new CliOption(CodegenConstants.USE_ONEOF_DISCRIMINATOR_LOOKUP, CodegenConstants.USE_ONEOF_DISCRIMINATOR_LOOKUP_DESC).defaultValue("false"));
154154
cliOptions.add(new CliOption(POETRY1_FALLBACK, "Fallback to formatting pyproject.toml to Poetry 1.x format."));
155155
cliOptions.add(new CliOption(LAZY_IMPORTS, "Enable lazy imports.").defaultValue(Boolean.FALSE.toString()));
156+
cliOptions.add(new CliOption(CodegenConstants.ENUM_UNKNOWN_DEFAULT_CASE, CodegenConstants.ENUM_UNKNOWN_DEFAULT_CASE_DESC).defaultValue("false"));
156157

157158
supportedLibraries.put("urllib3", "urllib3-based client");
158159
supportedLibraries.put("asyncio", "asyncio-based client");
@@ -271,6 +272,10 @@ public void processOpts() {
271272
additionalProperties.put(LAZY_IMPORTS, Boolean.valueOf(additionalProperties.get(LAZY_IMPORTS).toString()));
272273
}
273274

275+
if (additionalProperties.containsKey(CodegenConstants.ENUM_UNKNOWN_DEFAULT_CASE)) {
276+
setEnumUnknownDefaultCase(Boolean.parseBoolean(additionalProperties.get(CodegenConstants.ENUM_UNKNOWN_DEFAULT_CASE).toString()));
277+
}
278+
274279
String modelPath = packagePath() + File.separatorChar + modelPackage.replace('.', File.separatorChar);
275280
String apiPath = packagePath() + File.separatorChar + apiPackage.replace('.', File.separatorChar);
276281

modules/openapi-generator/src/main/resources/python/model_enum.mustache

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,13 @@ class {{classname}}({{vendorExtensions.x-py-enum-type}}, Enum):
2424
def from_json(cls, json_str: str) -> Self:
2525
"""Create an instance of {{classname}} from a JSON string"""
2626
return cls(json.loads(json_str))
27+
{{#enumUnknownDefaultCase}}
28+
29+
@classmethod
30+
def _missing_(cls, value):
31+
"""Handle unknown enum values"""
32+
return cls.UNKNOWN_DEFAULT_OPEN_API
33+
{{/enumUnknownDefaultCase}}
2734

2835
{{#defaultValue}}
2936

modules/openapi-generator/src/test/java/org/openapitools/codegen/python/PythonClientCodegenTest.java

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@
2626
import io.swagger.v3.parser.util.SchemaTypeUtil;
2727
import org.openapitools.codegen.*;
2828
import org.openapitools.codegen.config.CodegenConfigurator;
29+
import org.openapitools.codegen.model.ModelMap;
30+
import org.openapitools.codegen.model.ModelsMap;
2931
import org.openapitools.codegen.languages.PythonClientCodegen;
3032
import org.openapitools.codegen.languages.features.CXFServerFeatures;
3133
import org.testng.Assert;
@@ -38,6 +40,8 @@
3840
import java.nio.file.Paths;
3941
import java.util.ArrayList;
4042
import java.util.Arrays;
43+
import java.util.Collections;
44+
import java.util.HashMap;
4145
import java.util.List;
4246
import java.util.Map;
4347
import java.util.function.Function;
@@ -47,6 +51,7 @@
4751
import static org.junit.jupiter.api.Assertions.assertNull;
4852
import static org.openapitools.codegen.TestUtils.assertFileContains;
4953
import static org.openapitools.codegen.TestUtils.assertFileExists;
54+
import static org.openapitools.codegen.TestUtils.assertFileNotContains;
5055

5156
public class PythonClientCodegenTest {
5257

@@ -685,4 +690,120 @@ public void testNonPoetry1LicenseFormat() throws IOException {
685690
// Verify it does NOT use the legacy string format
686691
TestUtils.assertFileNotContains(pyprojectPath, "license = \"BSD-3-Clause\"");
687692
}
693+
694+
@Test(description = "test enumUnknownDefaultCase option")
695+
public void testEnumUnknownDefaultCaseOption() {
696+
final PythonClientCodegen codegen = new PythonClientCodegen();
697+
698+
// Test default value is false
699+
codegen.processOpts();
700+
Assert.assertEquals(codegen.getEnumUnknownDefaultCase(), Boolean.FALSE);
701+
702+
// Test setting via additionalProperties
703+
codegen.additionalProperties().put(CodegenConstants.ENUM_UNKNOWN_DEFAULT_CASE, "true");
704+
codegen.processOpts();
705+
Assert.assertEquals(codegen.getEnumUnknownDefaultCase(), Boolean.TRUE);
706+
}
707+
708+
@Test(description = "test enum model generation with enumUnknownDefaultCase")
709+
public void testEnumModelWithUnknownDefaultCase() {
710+
final OpenAPI openAPI = TestUtils.parseFlattenSpec("src/test/resources/3_0/enum_unknown_default_case.yaml");
711+
final PythonClientCodegen codegen = new PythonClientCodegen();
712+
713+
// Enable enumUnknownDefaultCase
714+
codegen.additionalProperties().put(CodegenConstants.ENUM_UNKNOWN_DEFAULT_CASE, "true");
715+
codegen.setOpenAPI(openAPI);
716+
codegen.processOpts();
717+
718+
// Verify that enumUnknownDefaultCase is set
719+
Assert.assertEquals(codegen.getEnumUnknownDefaultCase(), Boolean.TRUE);
720+
721+
// Process all models to trigger enum processing
722+
Map<String, Schema> schemas = openAPI.getComponents().getSchemas();
723+
Map<String, ModelsMap> allModels = new HashMap<>();
724+
for (String modelName : schemas.keySet()) {
725+
Schema schema = schemas.get(modelName);
726+
CodegenModel cm = codegen.fromModel(modelName, schema);
727+
ModelsMap modelsMap = new ModelsMap();
728+
modelsMap.setModels(Collections.singletonList(new ModelMap(Collections.singletonMap("model", cm))));
729+
allModels.put(modelName, modelsMap);
730+
}
731+
732+
// Post-process to add enumVars
733+
allModels = codegen.postProcessAllModels(allModels);
734+
735+
// Get the ColorEnum model
736+
CodegenModel colorEnum = null;
737+
for (Map.Entry<String, ModelsMap> entry : allModels.entrySet()) {
738+
if ("ColorEnum".equals(entry.getKey())) {
739+
colorEnum = entry.getValue().getModels().get(0).getModel();
740+
break;
741+
}
742+
}
743+
744+
Assert.assertNotNull(colorEnum);
745+
Assert.assertNotNull(colorEnum.allowableValues);
746+
747+
List<Map<String, Object>> enumVars = (List<Map<String, Object>>) colorEnum.allowableValues.get("enumVars");
748+
Assert.assertNotNull(enumVars);
749+
750+
// Check that we have the expected enum values including UNKNOWN_DEFAULT_OPEN_API
751+
Assert.assertTrue(enumVars.stream().anyMatch(var -> "'RED'".equals(var.get("value"))));
752+
Assert.assertTrue(enumVars.stream().anyMatch(var -> "'GREEN'".equals(var.get("value"))));
753+
Assert.assertTrue(enumVars.stream().anyMatch(var -> "'BLUE'".equals(var.get("value"))));
754+
Assert.assertTrue(enumVars.stream().anyMatch(var -> "'YELLOW'".equals(var.get("value"))));
755+
Assert.assertTrue(enumVars.stream().anyMatch(var -> "'unknown_default_open_api'".equals(var.get("value"))));
756+
}
757+
758+
@Test(description = "test enum generation with enumUnknownDefaultCase enabled")
759+
public void testEnumGenerationWithUnknownDefaultCase() throws IOException {
760+
File output = Files.createTempDirectory("test").toFile().getCanonicalFile();
761+
output.deleteOnExit();
762+
String outputPath = output.getAbsolutePath().replace('\\', '/');
763+
764+
final CodegenConfigurator configurator = new CodegenConfigurator()
765+
.setGeneratorName("python")
766+
.setInputSpec("src/test/resources/3_0/enum_unknown_default_case.yaml")
767+
.setOutputDir(outputPath)
768+
.addAdditionalProperty(CodegenConstants.ENUM_UNKNOWN_DEFAULT_CASE, "true");
769+
770+
DefaultGenerator generator = new DefaultGenerator();
771+
List<File> files = generator.opts(configurator.toClientOptInput()).generate();
772+
files.forEach(File::deleteOnExit);
773+
774+
Path enumFile = Paths.get(outputPath, "openapi_client", "models", "color_enum.py");
775+
776+
// Check that UNKNOWN_DEFAULT_OPEN_API is added (with single quotes as Python generates)
777+
TestUtils.assertFileContains(enumFile, "UNKNOWN_DEFAULT_OPEN_API = 'unknown_default_open_api'");
778+
779+
// Check that _missing_ method is added
780+
TestUtils.assertFileContains(enumFile, "@classmethod");
781+
TestUtils.assertFileContains(enumFile, "def _missing_(cls, value):");
782+
TestUtils.assertFileContains(enumFile, "return cls.UNKNOWN_DEFAULT_OPEN_API");
783+
}
784+
785+
@Test(description = "test enum generation with enumUnknownDefaultCase disabled")
786+
public void testEnumGenerationWithoutUnknownDefaultCase() throws IOException {
787+
File output = Files.createTempDirectory("test").toFile().getCanonicalFile();
788+
output.deleteOnExit();
789+
String outputPath = output.getAbsolutePath().replace('\\', '/');
790+
791+
final CodegenConfigurator configurator = new CodegenConfigurator()
792+
.setGeneratorName("python")
793+
.setInputSpec("src/test/resources/3_0/enum_unknown_default_case.yaml")
794+
.setOutputDir(outputPath)
795+
.addAdditionalProperty(CodegenConstants.ENUM_UNKNOWN_DEFAULT_CASE, "false");
796+
797+
DefaultGenerator generator = new DefaultGenerator();
798+
List<File> files = generator.opts(configurator.toClientOptInput()).generate();
799+
files.forEach(File::deleteOnExit);
800+
801+
Path enumFile = Paths.get(outputPath, "openapi_client", "models", "color_enum.py");
802+
803+
// Check that UNKNOWN_DEFAULT_OPEN_API is NOT added
804+
TestUtils.assertFileNotContains(enumFile, "UNKNOWN_DEFAULT_OPEN_API");
805+
806+
// Check that _missing_ method is NOT added
807+
TestUtils.assertFileNotContains(enumFile, "def _missing_(cls, value):");
808+
}
688809
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
openapi: 3.0.0
2+
info:
3+
title: Enum Test API
4+
description: API for testing enum generation with enumUnknownDefaultCase
5+
version: 1.0.0
6+
paths:
7+
/colors:
8+
get:
9+
summary: Get color
10+
operationId: getColor
11+
responses:
12+
'200':
13+
description: Successful response
14+
content:
15+
application/json:
16+
schema:
17+
$ref: '#/components/schemas/ColorResponse'
18+
components:
19+
schemas:
20+
ColorResponse:
21+
type: object
22+
required:
23+
- color
24+
- status
25+
properties:
26+
color:
27+
$ref: '#/components/schemas/ColorEnum'
28+
status:
29+
$ref: '#/components/schemas/StatusEnum'
30+
priority:
31+
$ref: '#/components/schemas/PriorityEnum'
32+
ColorEnum:
33+
type: string
34+
description: Available colors
35+
enum:
36+
- RED
37+
- GREEN
38+
- BLUE
39+
- YELLOW
40+
StatusEnum:
41+
type: string
42+
description: Status values
43+
enum:
44+
- PENDING
45+
- APPROVED
46+
- REJECTED
47+
- IN_PROGRESS
48+
PriorityEnum:
49+
type: integer
50+
description: Priority levels
51+
enum:
52+
- 1
53+
- 2
54+
- 3
55+
- 4
56+
- 5

0 commit comments

Comments
 (0)