Skip to content
Open
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 @@ -216,20 +216,21 @@ public String toDefaultValue(Schema p) {
if (p.getDefault() != null) {
String defaultValue = String.valueOf(p.getDefault());
if (defaultValue != null) {
// String enums (type: string + enum) must be quoted: templates embed defaultValue
// directly into Query()/Header()/Cookie() calls (e.g. Query('B', ...)). Without
// quotes, values like uploadTime or desc become bare identifiers and cause NameError.
// See https://github.com/OpenAPITools/openapi-generator/issues/23774
if (ModelUtils.isStringSchema(p)) {
return formatPythonStringLiteral(defaultValue);
}
return defaultValue;
}
}
} else if (ModelUtils.isStringSchema(p)) {
if (p.getDefault() != null) {
String defaultValue = String.valueOf(p.getDefault());
if (defaultValue != null) {
defaultValue = defaultValue.replace("\\", "\\\\")
.replace("'", "\\'");
if (Pattern.compile("\r\n|\r|\n").matcher(defaultValue).find()) {
return "'''" + defaultValue + "'''";
} else {
return "'" + defaultValue + "'";
}
return formatPythonStringLiteral(defaultValue);
}
}
} else if (ModelUtils.isArraySchema(p)) {
Expand All @@ -242,6 +243,55 @@ public String toDefaultValue(Schema p) {
return null;
}

/**
* Format a string as a Python string literal (single-quoted).
* Shared by plain string and string-enum default value paths in {@link #toDefaultValue(Schema)}.
*/
protected String formatPythonStringLiteral(String defaultValue) {
defaultValue = defaultValue.replace("\\", "\\\\")
.replace("'", "\\'");
if (Pattern.compile("\r\n|\r|\n").matcher(defaultValue).find()) {
return "'''" + defaultValue + "'''";
}
return "'" + defaultValue + "'";
}

/**
* If {@code value} is already a Python string literal, return the unescaped inner content.
* Used when reconciling quoted defaults from {@link #toDefaultValue(Schema)} with enum-var
* matching in {@link #getEnumDefaultValue(String, String)}.
*/
protected String unwrapPythonStringLiteral(String value) {
if (value == null) {
return null;
}
if (value.length() >= 6 && value.startsWith("'''") && value.endsWith("'''")) {
return value.substring(3, value.length() - 3);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Multiline string enum defaults containing an apostrophe or backslash can stop resolving to the generated enum member. The triple-quoted unwrap path returns the escaped contents unchanged, so getEnumDefaultValue compares a double-escaped literal against enumVars; consider unescaping this branch the same way as the single-quoted branch.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractPythonCodegen.java, line 269:

<comment>Multiline string enum defaults containing an apostrophe or backslash can stop resolving to the generated enum member. The triple-quoted unwrap path returns the escaped contents unchanged, so `getEnumDefaultValue` compares a double-escaped literal against `enumVars`; consider unescaping this branch the same way as the single-quoted branch.</comment>

<file context>
@@ -242,6 +243,55 @@ public String toDefaultValue(Schema p) {
+            return null;
+        }
+        if (value.length() >= 6 && value.startsWith("'''") && value.endsWith("'''")) {
+            return value.substring(3, value.length() - 3);
+        }
+        if (value.length() >= 2 && value.startsWith("'") && value.endsWith("'")) {
</file context>
Suggested change
return value.substring(3, value.length() - 3);
return value.substring(3, value.length() - 3)
.replace("\\'", "'")
.replace("\\\\", "\\");

}
if (value.length() >= 2 && value.startsWith("'") && value.endsWith("'")) {
return value.substring(1, value.length() - 1)
.replace("\\'", "'")
.replace("\\\\", "\\");
}
return null;
}

/**
* Normalize enum default values before matching against enum member literals.
* {@link #toDefaultValue(Schema)} now quotes string-enum defaults (e.g. {@code 'B'}), but
* {@code updateCodegenPropertyEnum} compares against unquoted enum values via
* {@code toEnumValue}. Unwrap first so model properties still resolve to EnumClass.MEMBER.
*/
@Override
protected String getEnumDefaultValue(String defaultValue, String dataType) {
if (isDataTypeString(dataType)) {
String unquoted = unwrapPythonStringLiteral(defaultValue);
if (unquoted != null) {
defaultValue = unquoted;
}
}
return super.getEnumDefaultValue(defaultValue, dataType);
}

@Override
public String toVarName(String name) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,26 @@ public void testBackslashDefault() {
Assert.assertEquals("'\\\\'", defaultValue);
}

@Test(description = "test string enum default is quoted")
public void testStringEnumDefaultIsQuoted() {
final PythonClientCodegen codegen = new PythonClientCodegen();
StringSchema schema = new StringSchema();
schema.setEnum(Arrays.asList("uploadTime", "duration", "fileSize"));
schema.setDefault("uploadTime");
String defaultValue = codegen.toDefaultValue(schema);
Assert.assertEquals("'uploadTime'", defaultValue);
}

@Test(description = "test string enum default with special chars is quoted")
public void testStringEnumDefaultFalseIsQuoted() {
final PythonClientCodegen codegen = new PythonClientCodegen();
StringSchema schema = new StringSchema();
schema.setEnum(Arrays.asList("true", "false"));
schema.setDefault("false");
String defaultValue = codegen.toDefaultValue(schema);
Assert.assertEquals("'false'", defaultValue);
}

@Test(description = "convert a python model with dots")
public void modelTest() {
final OpenAPI openAPI = TestUtils.parseFlattenSpec("src/test/resources/3_0/v1beta3.yaml");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,36 @@ public void testNoAdditionalSlashesInQueryRegex() throws IOException {
"r\"^[a-zA-Z0-9]+[a-zA-Z0-9\\.\\-_]*[a-zA-Z0-9]+$\"");
}

@Test
public void testStringEnumQueryParameterDefaultsAreQuoted() throws IOException {
// Regression for https://github.com/OpenAPITools/openapi-generator/issues/23774
// Repro spec: src/test/resources/3_0/parameter-test-spec.yaml (query_default_enum, etc.)
File output = Files.createTempDirectory("test").toFile();
output.deleteOnExit();

final CodegenConfigurator configurator = new CodegenConfigurator()
.setGeneratorName("python-fastapi")
.setOutputDir(output.getAbsolutePath().replace("\\", "/"))
.setInputSpec("src/test/resources/3_0/parameter-test-spec.yaml");

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

final String apiFile = output + "/src/openapi_server/apis/default_api.py";
TestUtils.assertFileContains(Paths.get(apiFile),
"= Query('available', description=\"query default\", alias=\"query_default\"),");
TestUtils.assertFileContains(Paths.get(apiFile),
"= Query('B', description=\"query default enum\", alias=\"query_default_enum\"),");
TestUtils.assertFileContains(Paths.get(apiFile),
"= Header('B', description=\"header default enum\"),");
TestUtils.assertFileContains(Paths.get(apiFile),
"= Cookie('B', description=\"cookie default enum\"),");
TestUtils.assertFileNotContains(Paths.get(apiFile), "= Query(B, description=\"query default enum\"");
TestUtils.assertFileNotContains(Paths.get(apiFile), "= Header(B, description=\"header default enum\"");
TestUtils.assertFileNotContains(Paths.get(apiFile), "= Cookie(B, description=\"cookie default enum\"");
}

@Test
public void testRegexPatternCheckedForArrayItems() throws IOException {
File output = Files.createTempDirectory("test").toFile();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ configuration = petstore_api.Configuration(
async with petstore_api.ApiClient(configuration) as api_client:
# Create an instance of the API class
api_instance = petstore_api.FakeApi(api_client)
enum_ref = -efg # EnumClass | enum reference (optional) (default to -efg)
enum_ref = '-efg' # EnumClass | enum reference (optional) (default to '-efg')

try:
# test enum reference query parameter
Expand All @@ -147,7 +147,7 @@ async with petstore_api.ApiClient(configuration) as api_client:

Name | Type | Description | Notes
------------- | ------------- | ------------- | -------------
**enum_ref** | [**EnumClass**](.md)| enum reference | [optional] [default to -efg]
**enum_ref** | [**EnumClass**](.md)| enum reference | [optional] [default to &#39;-efg&#39;]

### Return type

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ configuration = petstore_api.Configuration(
async with petstore_api.ApiClient(configuration) as api_client:
# Create an instance of the API class
api_instance = petstore_api.FakeApi(api_client)
enum_ref = -efg # EnumClass | enum reference (optional) (default to -efg)
enum_ref = '-efg' # EnumClass | enum reference (optional) (default to '-efg')

try:
# test enum reference query parameter
Expand All @@ -151,7 +151,7 @@ async with petstore_api.ApiClient(configuration) as api_client:

Name | Type | Description | Notes
------------- | ------------- | ------------- | -------------
**enum_ref** | [**EnumClass**](.md)| enum reference | [optional] [default to -efg]
**enum_ref** | [**EnumClass**](.md)| enum reference | [optional] [default to &#39;-efg&#39;]

### Return type

Expand Down
4 changes: 2 additions & 2 deletions samples/openapi3/client/petstore/python-httpx/docs/FakeApi.md
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ configuration = petstore_api.Configuration(
async with petstore_api.ApiClient(configuration) as api_client:
# Create an instance of the API class
api_instance = petstore_api.FakeApi(api_client)
enum_ref = -efg # EnumClass | enum reference (optional) (default to -efg)
enum_ref = '-efg' # EnumClass | enum reference (optional) (default to '-efg')

try:
# test enum reference query parameter
Expand All @@ -147,7 +147,7 @@ async with petstore_api.ApiClient(configuration) as api_client:

Name | Type | Description | Notes
------------- | ------------- | ------------- | -------------
**enum_ref** | [**EnumClass**](.md)| enum reference | [optional] [default to -efg]
**enum_ref** | [**EnumClass**](.md)| enum reference | [optional] [default to &#39;-efg&#39;]

### Return type

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ configuration = petstore_api.Configuration(
with petstore_api.ApiClient(configuration) as api_client:
# Create an instance of the API class
api_instance = petstore_api.FakeApi(api_client)
enum_ref = -efg # EnumClass | enum reference (optional) (default to -efg)
enum_ref = '-efg' # EnumClass | enum reference (optional) (default to '-efg')

try:
# test enum reference query parameter
Expand All @@ -147,7 +147,7 @@ with petstore_api.ApiClient(configuration) as api_client:

Name | Type | Description | Notes
------------- | ------------- | ------------- | -------------
**enum_ref** | [**EnumClass**](.md)| enum reference | [optional] [default to -efg]
**enum_ref** | [**EnumClass**](.md)| enum reference | [optional] [default to &#39;-efg&#39;]

### Return type

Expand Down
4 changes: 2 additions & 2 deletions samples/openapi3/client/petstore/python/docs/FakeApi.md
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ configuration = petstore_api.Configuration(
with petstore_api.ApiClient(configuration) as api_client:
# Create an instance of the API class
api_instance = petstore_api.FakeApi(api_client)
enum_ref = -efg # EnumClass | enum reference (optional) (default to -efg)
enum_ref = '-efg' # EnumClass | enum reference (optional) (default to '-efg')

try:
# test enum reference query parameter
Expand All @@ -147,7 +147,7 @@ with petstore_api.ApiClient(configuration) as api_client:

Name | Type | Description | Notes
------------- | ------------- | ------------- | -------------
**enum_ref** | [**EnumClass**](.md)| enum reference | [optional] [default to -efg]
**enum_ref** | [**EnumClass**](.md)| enum reference | [optional] [default to &#39;-efg&#39;]

### Return type

Expand Down
Loading