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
1 change: 1 addition & 0 deletions bin/configs/python-aiohttp.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ additionalProperties:
packageName: petstore_api
mapNumberTo: float
poetry1: true
enumUnknownDefaultCase: true
nameMappings:
_type: underscore_type
type_: type_with_underscore
Expand Down
1 change: 1 addition & 0 deletions docs/generators/python.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.|<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|
|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|
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand Down Expand Up @@ -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);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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}}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -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 {

Expand Down Expand Up @@ -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<String, Schema> schemas = openAPI.getComponents().getSchemas();
Map<String, ModelsMap> 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<String, ModelsMap> entry : allModels.entrySet()) {
if ("ColorEnum".equals(entry.getKey())) {
colorEnum = entry.getValue().getModels().get(0).getModel();
break;
}
}

Assert.assertNotNull(colorEnum);
Assert.assertNotNull(colorEnum.allowableValues);

List<Map<String, Object>> enumVars = (List<Map<String, Object>>) colorEnum.allowableValues.get("enumVars");
Assert.assertNotNull(enumVars);

// Check that we have the expected enum values including UNKNOWN_DEFAULT_OPEN_API
Assert.assertTrue(enumVars.stream().anyMatch(var -> "'RED'".equals(var.get("value"))));
Assert.assertTrue(enumVars.stream().anyMatch(var -> "'GREEN'".equals(var.get("value"))));
Assert.assertTrue(enumVars.stream().anyMatch(var -> "'BLUE'".equals(var.get("value"))));
Assert.assertTrue(enumVars.stream().anyMatch(var -> "'YELLOW'".equals(var.get("value"))));
Assert.assertTrue(enumVars.stream().anyMatch(var -> "'unknown_default_open_api'".equals(var.get("value"))));
}

@Test(description = "test enum generation with enumUnknownDefaultCase enabled")
public void testEnumGenerationWithUnknownDefaultCase() throws IOException {
File output = Files.createTempDirectory("test").toFile().getCanonicalFile();
output.deleteOnExit();
String outputPath = output.getAbsolutePath().replace('\\', '/');

final CodegenConfigurator configurator = new CodegenConfigurator()
.setGeneratorName("python")
.setInputSpec("src/test/resources/3_0/enum_unknown_default_case.yaml")
.setOutputDir(outputPath)
.addAdditionalProperty(CodegenConstants.ENUM_UNKNOWN_DEFAULT_CASE, "true");

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

Path enumFile = Paths.get(outputPath, "openapi_client", "models", "color_enum.py");

// Check that UNKNOWN_DEFAULT_OPEN_API is added (with single quotes as Python generates)
TestUtils.assertFileContains(enumFile, "UNKNOWN_DEFAULT_OPEN_API = 'unknown_default_open_api'");

// Check that _missing_ method is added
TestUtils.assertFileContains(enumFile, "@classmethod");
TestUtils.assertFileContains(enumFile, "def _missing_(cls, value):");
TestUtils.assertFileContains(enumFile, "return cls.UNKNOWN_DEFAULT_OPEN_API");
}

@Test(description = "test enum generation with enumUnknownDefaultCase disabled")
public void testEnumGenerationWithoutUnknownDefaultCase() throws IOException {
File output = Files.createTempDirectory("test").toFile().getCanonicalFile();
output.deleteOnExit();
String outputPath = output.getAbsolutePath().replace('\\', '/');

final CodegenConfigurator configurator = new CodegenConfigurator()
.setGeneratorName("python")
.setInputSpec("src/test/resources/3_0/enum_unknown_default_case.yaml")
.setOutputDir(outputPath)
.addAdditionalProperty(CodegenConstants.ENUM_UNKNOWN_DEFAULT_CASE, "false");

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

Path enumFile = Paths.get(outputPath, "openapi_client", "models", "color_enum.py");

// Check that UNKNOWN_DEFAULT_OPEN_API is NOT added
TestUtils.assertFileNotContains(enumFile, "UNKNOWN_DEFAULT_OPEN_API");

// Check that _missing_ method is NOT added
TestUtils.assertFileNotContains(enumFile, "def _missing_(cls, value):");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
openapi: 3.0.0
info:
title: Enum Test API
description: API for testing enum generation with enumUnknownDefaultCase
version: 1.0.0
paths:
/colors:
get:
summary: Get color
operationId: getColor
responses:
'200':
description: Successful response
content:
application/json:
schema:
$ref: '#/components/schemas/ColorResponse'
components:
schemas:
ColorResponse:
type: object
required:
- color
- status
properties:
color:
$ref: '#/components/schemas/ColorEnum'
status:
$ref: '#/components/schemas/StatusEnum'
priority:
$ref: '#/components/schemas/PriorityEnum'
ColorEnum:
type: string
description: Available colors
enum:
- RED
- GREEN
- BLUE
- YELLOW
StatusEnum:
type: string
description: Status values
enum:
- PENDING
- APPROVED
- REJECTED
- IN_PROGRESS
PriorityEnum:
type: integer
description: Priority levels
enum:
- 1
- 2
- 3
- 4
- 5
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@

* `XML` (value: `'XML'`)

* `UNKNOWN_DEFAULT_OPEN_API` (value: `'unknown_default_open_api'`)

[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)


Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@

* `LEFT_PARENTHESIS_XYZ_RIGHT_PARENTHESIS` (value: `'(xyz)'`)

* `UNKNOWN_DEFAULT_OPEN_API` (value: `'unknown_default_open_api'`)

[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)


Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@

* `FiftySix` (value: `56`)

* `unknown_default_open_api` (value: `11184809`)

[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)


Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@

* `B` (value: `'b'`)

* `UNKNOWN_DEFAULT_OPEN_API` (value: `'unknown_default_open_api'`)

[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)


Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@

* `D` (value: `'d'`)

* `UNKNOWN_DEFAULT_OPEN_API` (value: `'unknown_default_open_api'`)

[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)


Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@

* `bazVar_XEnumVarname` (value: `'baz'`)

* `&#39;unknown_default_open_api&#39;` (value: `'unknown_default_open_api'`)

[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)


Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ oneOf enum strings

* `D` (value: `'d'`)

* `UNKNOWN_DEFAULT_OPEN_API` (value: `'unknown_default_open_api'`)

[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)


Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@

* `DELIVERED` (value: `'delivered'`)

* `UNKNOWN_DEFAULT_OPEN_API` (value: `'unknown_default_open_api'`)

[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)


Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@

* `DELIVERED` (value: `'delivered'`)

* `UNKNOWN_DEFAULT_OPEN_API` (value: `'unknown_default_open_api'`)

[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)


Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@

* `NUMBER_2` (value: `2`)

* `NUMBER_11184809` (value: `11184809`)

[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)


Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@

* `NUMBER_2` (value: `2`)

* `NUMBER_11184809` (value: `11184809`)

[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)


Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@

* `USER` (value: `'user'`)

* `UNKNOWN_DEFAULT_OPEN_API` (value: `'unknown_default_open_api'`)

[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)


Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@

* `HELLO_WORLD` (value: `' hello world '`)

* `UNKNOWN_DEFAULT_OPEN_API` (value: `'unknown_default_open_api'`)

[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)


Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@

* `FOUR` (value: `'foUr'`)

* `UNKNOWN_DEFAULT_OPEN_API` (value: `'unknown_default_open_api'`)

[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)


Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@

* `DREI` (value: `'DREI'`)

* `UNKNOWN_DEFAULT_OPEN_API` (value: `'unknown_default_open_api'`)

[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)


2 changes: 2 additions & 0 deletions samples/openapi3/client/petstore/python-aiohttp/docs/Type.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@

* `NUMBER_0_DOT_25` (value: `0.25`)

* `NUMBER_11184809` (value: `11184809`)

[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)


Loading
Loading