diff --git a/docs/generators/python.md b/docs/generators/python.md
index 10a032dadacb..3aab0db3ab52 100644
--- a/docs/generators/python.md
+++ b/docs/generators/python.md
@@ -22,6 +22,7 @@ These options may be applied as additional-properties (cli) or configOptions (pl
|dateFormat|date format for query parameters| |%Y-%m-%d|
|datetimeFormat|datetime format for query parameters| |%Y-%m-%dT%H:%M:%S%z|
|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.|
- **false**
- The 'additionalProperties' implementation is compliant with the OAS and JSON schema specifications.
- **true**
- Keep the old (incorrect) behaviour that 'additionalProperties' is set to false by default.
|true|
+|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|
|generateSourceCodeOnly|Specifies that only a library source code is to be generated.| |false|
|hideGenerationTimestamp|Hides the generation timestamp when files are generated.| |true|
|lazyImports|Enable lazy imports.| |false|
diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/PythonClientCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/PythonClientCodegen.java
index 149ce57c317f..c5d1d77411e4 100644
--- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/PythonClientCodegen.java
+++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/PythonClientCodegen.java
@@ -153,6 +153,7 @@ public PythonClientCodegen() {
cliOptions.add(new CliOption(CodegenConstants.USE_ONEOF_DISCRIMINATOR_LOOKUP, CodegenConstants.USE_ONEOF_DISCRIMINATOR_LOOKUP_DESC).defaultValue("false"));
cliOptions.add(new CliOption(POETRY1_FALLBACK, "Fallback to formatting pyproject.toml to Poetry 1.x format."));
cliOptions.add(new CliOption(LAZY_IMPORTS, "Enable lazy imports.").defaultValue(Boolean.FALSE.toString()));
+ cliOptions.add(new CliOption(CodegenConstants.ENUM_UNKNOWN_DEFAULT_CASE, CodegenConstants.ENUM_UNKNOWN_DEFAULT_CASE_DESC).defaultValue("false"));
supportedLibraries.put("urllib3", "urllib3-based client");
supportedLibraries.put("asyncio", "asyncio-based client");
@@ -271,6 +272,10 @@ public void processOpts() {
additionalProperties.put(LAZY_IMPORTS, Boolean.valueOf(additionalProperties.get(LAZY_IMPORTS).toString()));
}
+ if (additionalProperties.containsKey(CodegenConstants.ENUM_UNKNOWN_DEFAULT_CASE)) {
+ setEnumUnknownDefaultCase(Boolean.parseBoolean(additionalProperties.get(CodegenConstants.ENUM_UNKNOWN_DEFAULT_CASE).toString()));
+ }
+
String modelPath = packagePath() + File.separatorChar + modelPackage.replace('.', File.separatorChar);
String apiPath = packagePath() + File.separatorChar + apiPackage.replace('.', File.separatorChar);
diff --git a/modules/openapi-generator/src/main/resources/python/model_enum.mustache b/modules/openapi-generator/src/main/resources/python/model_enum.mustache
index 3f449b121a33..70307afae5a0 100644
--- a/modules/openapi-generator/src/main/resources/python/model_enum.mustache
+++ b/modules/openapi-generator/src/main/resources/python/model_enum.mustache
@@ -24,6 +24,13 @@ class {{classname}}({{vendorExtensions.x-py-enum-type}}, Enum):
def from_json(cls, json_str: str) -> Self:
"""Create an instance of {{classname}} from a JSON string"""
return cls(json.loads(json_str))
+ {{#enumUnknownDefaultCase}}
+
+ @classmethod
+ def _missing_(cls, value):
+ """Handle unknown enum values"""
+ return cls.UNKNOWN_DEFAULT_OPEN_API
+ {{/enumUnknownDefaultCase}}
{{#defaultValue}}
diff --git a/modules/openapi-generator/src/test/java/org/openapitools/codegen/python/PythonClientCodegenTest.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/python/PythonClientCodegenTest.java
index a18f2f40d11b..382cc2cebf50 100644
--- a/modules/openapi-generator/src/test/java/org/openapitools/codegen/python/PythonClientCodegenTest.java
+++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/python/PythonClientCodegenTest.java
@@ -26,6 +26,8 @@
import io.swagger.v3.parser.util.SchemaTypeUtil;
import org.openapitools.codegen.*;
import org.openapitools.codegen.config.CodegenConfigurator;
+import org.openapitools.codegen.model.ModelMap;
+import org.openapitools.codegen.model.ModelsMap;
import org.openapitools.codegen.languages.PythonClientCodegen;
import org.openapitools.codegen.languages.features.CXFServerFeatures;
import org.testng.Assert;
@@ -38,6 +40,8 @@
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
@@ -47,6 +51,7 @@
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.openapitools.codegen.TestUtils.assertFileContains;
import static org.openapitools.codegen.TestUtils.assertFileExists;
+import static org.openapitools.codegen.TestUtils.assertFileNotContains;
public class PythonClientCodegenTest {
@@ -685,4 +690,120 @@ public void testNonPoetry1LicenseFormat() throws IOException {
// Verify it does NOT use the legacy string format
TestUtils.assertFileNotContains(pyprojectPath, "license = \"BSD-3-Clause\"");
}
+
+ @Test(description = "test enumUnknownDefaultCase option")
+ public void testEnumUnknownDefaultCaseOption() {
+ final PythonClientCodegen codegen = new PythonClientCodegen();
+
+ // Test default value is false
+ codegen.processOpts();
+ Assert.assertEquals(codegen.getEnumUnknownDefaultCase(), Boolean.FALSE);
+
+ // Test setting via additionalProperties
+ codegen.additionalProperties().put(CodegenConstants.ENUM_UNKNOWN_DEFAULT_CASE, "true");
+ codegen.processOpts();
+ Assert.assertEquals(codegen.getEnumUnknownDefaultCase(), Boolean.TRUE);
+ }
+
+ @Test(description = "test enum model generation with enumUnknownDefaultCase")
+ public void testEnumModelWithUnknownDefaultCase() {
+ final OpenAPI openAPI = TestUtils.parseFlattenSpec("src/test/resources/3_0/enum_unknown_default_case.yaml");
+ final PythonClientCodegen codegen = new PythonClientCodegen();
+
+ // Enable enumUnknownDefaultCase
+ codegen.additionalProperties().put(CodegenConstants.ENUM_UNKNOWN_DEFAULT_CASE, "true");
+ codegen.setOpenAPI(openAPI);
+ codegen.processOpts();
+
+ // Verify that enumUnknownDefaultCase is set
+ Assert.assertEquals(codegen.getEnumUnknownDefaultCase(), Boolean.TRUE);
+
+ // Process all models to trigger enum processing
+ Map schemas = openAPI.getComponents().getSchemas();
+ Map allModels = new HashMap<>();
+ for (String modelName : schemas.keySet()) {
+ Schema schema = schemas.get(modelName);
+ CodegenModel cm = codegen.fromModel(modelName, schema);
+ ModelsMap modelsMap = new ModelsMap();
+ modelsMap.setModels(Collections.singletonList(new ModelMap(Collections.singletonMap("model", cm))));
+ allModels.put(modelName, modelsMap);
+ }
+
+ // Post-process to add enumVars
+ allModels = codegen.postProcessAllModels(allModels);
+
+ // Get the ColorEnum model
+ CodegenModel colorEnum = null;
+ for (Map.Entry entry : allModels.entrySet()) {
+ if ("ColorEnum".equals(entry.getKey())) {
+ colorEnum = entry.getValue().getModels().get(0).getModel();
+ break;
+ }
+ }
+
+ Assert.assertNotNull(colorEnum);
+ Assert.assertNotNull(colorEnum.allowableValues);
+
+ List