diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/PythonExperimentalClientCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/PythonExperimentalClientCodegen.java index 95d6d5bc690c..8dabf7030e6d 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/PythonExperimentalClientCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/PythonExperimentalClientCodegen.java @@ -17,6 +17,8 @@ package org.openapitools.codegen.languages; import com.github.curiousoddman.rgxgen.RgxGen; +import com.github.curiousoddman.rgxgen.config.RgxGenOption; +import com.github.curiousoddman.rgxgen.config.RgxGenProperties; import io.swagger.v3.oas.models.OpenAPI; import io.swagger.v3.oas.models.Operation; import io.swagger.v3.oas.models.servers.Server; @@ -611,7 +613,7 @@ public class PythonExperimentalClientCodegen extends AbstractPythonCodegen { } else { strValue = dateValue.toString(); } - return "isoparse('" + strValue + "').date()"; + return strValue; } public String pythonDateTime(Object dateTimeValue) { @@ -628,7 +630,7 @@ public class PythonExperimentalClientCodegen extends AbstractPythonCodegen { } else { strValue = dateTimeValue.toString(); } - return "isoparse('" + strValue + "')"; + return strValue; } /** @@ -1355,6 +1357,13 @@ public class PythonExperimentalClientCodegen extends AbstractPythonCodegen { return "\"" + in + "\""; } + @Override + public String toExampleValue(Schema schema) { + String modelName = getModelName(schema); + Object objExample = getObjectExample(schema); + return toExampleValueRecursive(modelName, schema, objExample, 1, "", 0, new ArrayList<>()); + } + public String toExampleValue(Schema schema, Object objExample) { String modelName = getModelName(schema); return toExampleValueRecursive(modelName, schema, objExample, 1, "", 0, new ArrayList<>()); @@ -1447,12 +1456,66 @@ public class PythonExperimentalClientCodegen extends AbstractPythonCodegen { } String refModelName = getModelName(schema); return toExampleValueRecursive(refModelName, refSchema, objExample, indentationLevel, prefix, exampleLine, includedSchemas); - } else if (ModelUtils.isNullType(schema) || isAnyTypeSchema(schema)) { + } else if (ModelUtils.isNullType(schema)) { // The 'null' type is allowed in OAS 3.1 and above. It is not supported by OAS 3.0.x, // though this tooling supports it. return fullPrefix + "None" + closeChars; + } else if (ModelUtils.isAnyType(schema)) { + /* + This schema may be a composed schema + TODO generate examples for some of these use cases in the future like + only oneOf without a discriminator + */ + Boolean hasProperties = (schema.getProperties() != null && !schema.getProperties().isEmpty()); + CodegenDiscriminator disc = createDiscriminator(modelName, schema, openAPI); + if (ModelUtils.isComposedSchema(schema)) { + // complex composed object type schemas not yet handled and the code returns early + if (hasProperties) { + // what if this composed schema defined properties + allOf? + // or items + properties, both a ist and a dict could be accepted as payloads + return fullPrefix + "{}" + closeChars; + } + ComposedSchema cs = (ComposedSchema) schema; + Integer allOfExists = 0; + if (cs.getAllOf() != null && !cs.getAllOf().isEmpty()) { + allOfExists = 1; + } + Integer anyOfExists = 0; + if (cs.getAnyOf() != null && !cs.getAnyOf().isEmpty()) { + anyOfExists = 1; + } + Integer oneOfExists = 0; + if (cs.getOneOf() != null && !cs.getOneOf().isEmpty()) { + oneOfExists = 1; + } + if (allOfExists + anyOfExists + oneOfExists > 1) { + // what if it needs one oneOf schema, one anyOf schema, and two allOf schemas? + return fullPrefix + "None" + closeChars; + } + // for now only oneOf with discriminator is supported + if (oneOfExists == 1 && disc != null) { + ; + } else { + return fullPrefix + "None" + closeChars; + } + } + if (disc != null) { + // a discriminator means that the type must be object + MappedModel mm = getDiscriminatorMappedModel(disc); + if (mm == null) { + return fullPrefix + "None" + closeChars; + } + String discPropNameValue = mm.getMappingName(); + String chosenModelName = mm.getModelName(); + Schema modelSchema = getModelNameToSchemaCache().get(chosenModelName); + CodegenProperty cp = new CodegenProperty(); + cp.setName(disc.getPropertyName()); + cp.setExample(discPropNameValue); + return exampleForObjectModel(modelSchema, fullPrefix, closeChars, cp, indentationLevel, exampleLine, closingIndentation, includedSchemas); + } + return fullPrefix + "None" + closeChars; } else if (ModelUtils.isBooleanSchema(schema)) { - if (objExample == null) { + if (example == null) { example = "True"; } else { if ("false".equalsIgnoreCase(objExample.toString())) { @@ -1462,60 +1525,82 @@ public class PythonExperimentalClientCodegen extends AbstractPythonCodegen { } } return fullPrefix + example + closeChars; - } else if (ModelUtils.isDateSchema(schema)) { - if (objExample == null) { - example = pythonDate("1970-01-01"); - } else { - example = pythonDate(objExample); - } - return fullPrefix + example + closeChars; - } else if (ModelUtils.isDateTimeSchema(schema)) { - if (objExample == null) { - example = pythonDateTime("1970-01-01T00:00:00.00Z"); - } else { - example = pythonDateTime(objExample); - } - return fullPrefix + example + closeChars; - } else if (ModelUtils.isBinarySchema(schema)) { - if (objExample == null) { - example = "/path/to/file"; - } - example = "open('" + example + "', 'rb')"; - return fullPrefix + example + closeChars; - } else if (ModelUtils.isByteArraySchema(schema)) { - if (objExample == null) { - example = "'YQ=='"; - } - return fullPrefix + example + closeChars; } else if (ModelUtils.isStringSchema(schema)) { - if (objExample == null) { - // a BigDecimal: - if ("Number".equalsIgnoreCase(schema.getFormat())) { - example = "2"; - return fullPrefix + example + closeChars; - } else if (StringUtils.isNotBlank(schema.getPattern())) { - String pattern = schema.getPattern(); - RgxGen rgxGen = new RgxGen(pattern); - // this seed makes it so if we have [a-z] we pick a - Random random = new Random(18); - String sample = rgxGen.generate(random); - // omit leading / and trailing /, omit trailing /i - Pattern valueExtractor = Pattern.compile("^/?(.+?)/?.?$"); - Matcher m = valueExtractor.matcher(sample); - if (m.find()) { - example = m.group(m.groupCount()); - } else { - example = ""; - } - } else if (schema.getMinLength() != null) { - example = ""; - int len = schema.getMinLength().intValue(); - for (int i = 0; i < len; i++) example += "a"; - } else if (ModelUtils.isUUIDSchema(schema)) { - example = "046b6c7f-0b8a-43b9-b35d-6489e6daee91"; + if (example != null) { + return fullPrefix + ensureQuotes(example) + closeChars; + } + if (ModelUtils.isDateSchema(schema)) { + if (objExample == null) { + example = pythonDate("1970-01-01"); } else { - example = "string_example"; + example = pythonDate(objExample); } + } else if (ModelUtils.isDateTimeSchema(schema)) { + if (objExample == null) { + example = pythonDateTime("1970-01-01T00:00:00.00Z"); + } else { + example = pythonDateTime(objExample); + } + } else if (ModelUtils.isBinarySchema(schema)) { + if (example == null) { + example = "/path/to/file"; + } + example = "open('" + example + "', 'rb')"; + return fullPrefix + example + closeChars; + } else if (ModelUtils.isByteArraySchema(schema)) { + if (objExample == null) { + example = "'YQ=='"; + } + } else if ("Number".equalsIgnoreCase(schema.getFormat())) { + // a BigDecimal: + example = "2"; + } else if (StringUtils.isNotBlank(schema.getPattern())) { + String pattern = schema.getPattern(); + /* + RxGen does not support our ECMA dialect https://github.com/curious-odd-man/RgxGen/issues/56 + So strip off the leading / and trailing / and turn on ignore case if we have it + */ + Pattern valueExtractor = Pattern.compile("^/?(.+?)/?(.?)$"); + Matcher m = valueExtractor.matcher(pattern); + RgxGen rgxGen = null; + if (m.find()) { + int groupCount = m.groupCount(); + if (groupCount == 1) { + // only pattern found + String isolatedPattern = m.group(1); + rgxGen = new RgxGen(isolatedPattern); + } else if (groupCount == 2) { + // patterns and flag found + String isolatedPattern = m.group(1); + String flags = m.group(2); + if (flags.contains("i")) { + rgxGen = new RgxGen(isolatedPattern); + RgxGenProperties properties = new RgxGenProperties(); + RgxGenOption.CASE_INSENSITIVE.setInProperties(properties, true); + rgxGen.setProperties(properties); + } else { + rgxGen = new RgxGen(isolatedPattern); + } + } + } else { + rgxGen = new RgxGen(pattern); + } + + // this seed makes it so if we have [a-z] we pick a + Random random = new Random(18); + if (rgxGen != null) { + example = rgxGen.generate(random); + } else { + throw new RuntimeException("rgxGen cannot be null. Please open an issue in the openapi-generator github repo."); + } + } else if (schema.getMinLength() != null) { + example = ""; + int len = schema.getMinLength().intValue(); + for (int i = 0; i < len; i++) example += "a"; + } else if (ModelUtils.isUUIDSchema(schema)) { + example = "046b6c7f-0b8a-43b9-b35d-6489e6daee91"; + } else { + example = "string_example"; } return fullPrefix + ensureQuotes(example) + closeChars; } else if (ModelUtils.isIntegerSchema(schema)) { @@ -1539,7 +1624,11 @@ public class PythonExperimentalClientCodegen extends AbstractPythonCodegen { } else if (ModelUtils.isArraySchema(schema)) { if (objExample instanceof Iterable) { // If the example is already a list, return it directly instead of wrongly wrap it in another list - return fullPrefix + objExample.toString(); + return fullPrefix + objExample.toString() + closeChars; + } + if (ModelUtils.isComposedSchema(schema)) { + // complex composed array type schemas not yet handled and the code returns early + return fullPrefix + "[]" + closeChars; } ArraySchema arrayschema = (ArraySchema) schema; Schema itemSchema = arrayschema.getItems(); @@ -1547,18 +1636,68 @@ public class PythonExperimentalClientCodegen extends AbstractPythonCodegen { includedSchemas.add(schema); String itemExample = toExampleValueRecursive(itemModelName, itemSchema, objExample, indentationLevel + 1, "", exampleLine + 1, includedSchemas); if (StringUtils.isEmpty(itemExample) || cycleFound) { - return fullPrefix + "[]"; + return fullPrefix + "[]" + closeChars; } else { return fullPrefix + "[" + "\n" + itemExample + "\n" + closingIndentation + "]" + closeChars; } - } else if (ModelUtils.isMapSchema(schema)) { + } else if (ModelUtils.isTypeObjectSchema(schema)) { if (modelName == null) { fullPrefix += "dict("; closeChars = ")"; } + if (cycleFound) { + return fullPrefix + closeChars; + } + Boolean hasProperties = (schema.getProperties() != null && !schema.getProperties().isEmpty()); + CodegenDiscriminator disc = createDiscriminator(modelName, schema, openAPI); + if (ModelUtils.isComposedSchema(schema)) { + // complex composed object type schemas not yet handled and the code returns early + if (hasProperties) { + // what if this composed schema defined properties + allOf? + return fullPrefix + closeChars; + } + ComposedSchema cs = (ComposedSchema) schema; + Integer allOfExists = 0; + if (cs.getAllOf() != null && !cs.getAllOf().isEmpty()) { + allOfExists = 1; + } + Integer anyOfExists = 0; + if (cs.getAnyOf() != null && !cs.getAnyOf().isEmpty()) { + anyOfExists = 1; + } + Integer oneOfExists = 0; + if (cs.getOneOf() != null && !cs.getOneOf().isEmpty()) { + oneOfExists = 1; + } + if (allOfExists + anyOfExists + oneOfExists > 1) { + // what if it needs one oneOf schema, one anyOf schema, and two allOf schemas? + return fullPrefix + closeChars; + } + // for now only oneOf with discriminator is supported + if (oneOfExists == 1 && disc != null) { + ; + } else { + return fullPrefix + closeChars; + } + } + if (disc != null) { + MappedModel mm = getDiscriminatorMappedModel(disc); + if (mm == null) { + return fullPrefix + closeChars; + } + String discPropNameValue = mm.getMappingName(); + String chosenModelName = mm.getModelName(); + Schema modelSchema = getModelNameToSchemaCache().get(chosenModelName); + CodegenProperty cp = new CodegenProperty(); + cp.setName(disc.getPropertyName()); + cp.setExample(discPropNameValue); + return exampleForObjectModel(modelSchema, fullPrefix, closeChars, cp, indentationLevel, exampleLine, closingIndentation, includedSchemas); + } Object addPropsObj = schema.getAdditionalProperties(); - // TODO handle true case for additionalProperties - if (addPropsObj instanceof Schema && !cycleFound) { + if (hasProperties) { + return exampleForObjectModel(schema, fullPrefix, closeChars, null, indentationLevel, exampleLine, closingIndentation, includedSchemas); + } else if (addPropsObj instanceof Schema) { + // TODO handle true case for additionalProperties Schema addPropsSchema = (Schema) addPropsObj; String key = "key"; Object addPropsExample = getObjectExample(addPropsSchema); @@ -1576,51 +1715,6 @@ public class PythonExperimentalClientCodegen extends AbstractPythonCodegen { } else { example = fullPrefix + closeChars; } - return example; - } else if (ModelUtils.isObjectSchema(schema)) { - if (modelName == null) { - fullPrefix += "dict("; - closeChars = ")"; - } - if (cycleFound) { - return fullPrefix + closeChars; - } - CodegenDiscriminator disc = createDiscriminator(modelName, schema, openAPI); - if (disc != null) { - MappedModel mm = getDiscriminatorMappedModel(disc); - if (mm != null) { - String discPropNameValue = mm.getMappingName(); - String chosenModelName = mm.getModelName(); - // TODO handle this case in the future, this is when the discriminated - // schema allOf includes this schema, like Cat allOf includes Pet - // so this is the composed schema use case - } else { - return fullPrefix + closeChars; - } - } - return exampleForObjectModel(schema, fullPrefix, closeChars, null, indentationLevel, exampleLine, closingIndentation, includedSchemas); - } else if (ModelUtils.isComposedSchema(schema)) { - if (cycleFound) { - return fullPrefix + closeChars; - } - // TODO add examples for composed schema models without discriminators - - CodegenDiscriminator disc = createDiscriminator(modelName, schema, openAPI); - if (disc != null) { - MappedModel mm = getDiscriminatorMappedModel(disc); - if (mm != null) { - String discPropNameValue = mm.getMappingName(); - String chosenModelName = mm.getModelName(); - Schema modelSchema = getModelNameToSchemaCache().get(chosenModelName); - CodegenProperty cp = new CodegenProperty(); - cp.setName(disc.getPropertyName()); - cp.setExample(discPropNameValue); - return exampleForObjectModel(modelSchema, fullPrefix, closeChars, cp, indentationLevel, exampleLine, closingIndentation, includedSchemas); - } else { - return fullPrefix + closeChars; - } - } - return fullPrefix + closeChars; } else { LOGGER.warn("Type " + schema.getType() + " not handled properly in toExampleValue"); } diff --git a/samples/openapi3/client/petstore/python-experimental/docs/DateTimeTest.md b/samples/openapi3/client/petstore/python-experimental/docs/DateTimeTest.md index 408c95d0f9ae..4dd977eac981 100644 --- a/samples/openapi3/client/petstore/python-experimental/docs/DateTimeTest.md +++ b/samples/openapi3/client/petstore/python-experimental/docs/DateTimeTest.md @@ -2,7 +2,7 @@ Type | Description | Notes ------------- | ------------- | ------------- -**datetime** | | defaults to isoparse('2010-01-01T10:10:10.000111+01:00') +**datetime** | | defaults to 2010-01-01T10:10:10.000111+01:00 [[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) diff --git a/samples/openapi3/client/petstore/python-experimental/docs/FakeApi.md b/samples/openapi3/client/petstore/python-experimental/docs/FakeApi.md index 6633238a2dc9..72f722fb33e7 100644 --- a/samples/openapi3/client/petstore/python-experimental/docs/FakeApi.md +++ b/samples/openapi3/client/petstore/python-experimental/docs/FakeApi.md @@ -766,7 +766,7 @@ with petstore_api.ApiClient(configuration) as api_client: api_instance = fake_api.FakeApi(api_client) # example passing only optional values - body = ComposedOneOfDifferentTypes() + body = ComposedOneOfDifferentTypes(None) try: api_response = api_instance.composed_one_of_different_types( body=body, @@ -866,12 +866,12 @@ with petstore_api.ApiClient(configuration) as api_client: number=32.1, _float=3.14, double=67.8, - string="a", - pattern_without_delimiter="AUR,rZ#UM/?R,Fp^l6$ARjbhJk C", + string="A", + pattern_without_delimiter="Aj", byte='YQ==', binary=open('/path/to/file', 'rb'), - date=isoparse('1970-01-01').date(), - date_time=isoparse('2020-02-02T20:20:20.22222Z'), + date="1970-01-01", + date_time="2020-02-02T20:20:20.222220Z", password="password_example", callback="callback_example", ) @@ -911,7 +911,7 @@ Name | Type | Description | Notes **byte** | **str** | None | **binary** | **file_type** | None | [optional] **date** | **date** | None | [optional] -**dateTime** | **datetime** | None | [optional] if omitted the server will use the default value of isoparse('2010-02-01T10:20:10.11111+01:00') +**dateTime** | **datetime** | None | [optional] if omitted the server will use the default value of 2010-02-01T10:20:10.11111+01:00 **password** | **str** | None | [optional] **callback** | **str** | None | [optional] **any string name** | **bool, date, datetime, dict, float, int, list, str, none_type** | any string name can be used but the value must be the correct type | [optional] @@ -1434,12 +1434,12 @@ with petstore_api.ApiClient(configuration) as api_client: # example passing only optional values query_params = { - 'compositionAtRoot': , + 'compositionAtRoot': None, 'compositionInProperty': dict( - some_prop=, + some_prop=None, ), } - body = + body = None try: # testing composed schemas at inline locations api_response = api_instance.inline_composition( diff --git a/samples/openapi3/client/petstore/python-experimental/docs/StoreApi.md b/samples/openapi3/client/petstore/python-experimental/docs/StoreApi.md index 80f9a368f5fd..7ae16c17e9ba 100644 --- a/samples/openapi3/client/petstore/python-experimental/docs/StoreApi.md +++ b/samples/openapi3/client/petstore/python-experimental/docs/StoreApi.md @@ -314,7 +314,7 @@ with petstore_api.ApiClient(configuration) as api_client: id=1, pet_id=1, quantity=1, - ship_date=isoparse('2020-02-02T20:20:20.000222Z'), + ship_date="2020-02-02T20:20:20.000222Z", status="placed", complete=False, )