From d07f459ce35d3eaf54330e3d5e356a92ee32bfe1 Mon Sep 17 00:00:00 2001 From: Jiri Kuncar Date: Thu, 4 Jun 2020 03:26:30 +0200 Subject: [PATCH] [python] Fix date-time parsing (#6458) --- .../languages/PythonClientCodegen.java | 2 +- .../PythonClientExperimentalCodegen.java | 41 ++++++++++--------- .../python/PythonClientExperimentalTest.java | 12 ++++++ ...odels-for-testing-with-http-signature.yaml | 14 ++++++- .../python-experimental/docs/InlineObject3.md | 2 +- .../petstore_api/model/inline_object3.py | 2 +- 6 files changed, 49 insertions(+), 24 deletions(-) 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 5c0df0ab817..dffc0c62a19 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 @@ -722,7 +722,7 @@ public class PythonClientCodegen extends DefaultCodegen implements CodegenConfig } // correct "'"s into "'"s after toString() - if (ModelUtils.isStringSchema(schema) && schema.getDefault() != null) { + if (ModelUtils.isStringSchema(schema) && schema.getDefault() != null && !ModelUtils.isDateSchema(schema) && !ModelUtils.isDateTimeSchema(schema)) { example = (String) schema.getDefault(); } diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/PythonClientExperimentalCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/PythonClientExperimentalCodegen.java index 38496001238..6096b444ca3 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/PythonClientExperimentalCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/PythonClientExperimentalCodegen.java @@ -38,8 +38,9 @@ import org.openapitools.codegen.meta.Stability; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.text.DateFormat; -import java.text.SimpleDateFormat; +import java.time.OffsetDateTime; +import java.time.ZoneOffset; +import java.time.format.DateTimeFormatter; import java.io.File; import java.util.*; import java.util.regex.Pattern; @@ -196,15 +197,15 @@ public class PythonClientExperimentalCodegen extends PythonClientCodegen { return "python-experimental"; } - public String dateToString(Schema p, Date date, DateFormat dateFormatter, DateFormat dateTimeFormatter) { + public String dateToString(Schema p, OffsetDateTime date, DateTimeFormatter dateFormatter, DateTimeFormatter dateTimeFormatter) { // converts a date into a date or date-time python string if (!(ModelUtils.isDateSchema(p) || ModelUtils.isDateTimeSchema(p))) { throw new RuntimeException("passed schema must be of type Date or DateTime"); } if (ModelUtils.isDateSchema(p)) { - return "dateutil_parser('" + dateFormatter.format(date) + "').date()"; + return "dateutil_parser('" + date.format(dateFormatter) + "').date()"; } - return "dateutil_parser('" + dateTimeFormatter.format(date) + "')"; + return "dateutil_parser('" + date.format(dateTimeFormatter) + "')"; } /** @@ -228,20 +229,17 @@ public class PythonClientExperimentalCodegen extends PythonClientCodegen { } // convert datetime and date enums if they exist - DateFormat iso8601Date = new SimpleDateFormat("yyyy-MM-dd", Locale.ROOT); - DateFormat iso8601DateTime = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSXXX", Locale.ROOT); - TimeZone utc = TimeZone.getTimeZone("UTC"); - iso8601Date.setTimeZone(utc); - iso8601DateTime.setTimeZone(utc); + DateTimeFormatter iso8601Date = DateTimeFormatter.ISO_DATE; + DateTimeFormatter iso8601DateTime = DateTimeFormatter.ISO_DATE_TIME; if (ModelUtils.isDateSchema(p) || ModelUtils.isDateTimeSchema(p)) { List currentEnum = p.getEnum(); List fixedEnum = new ArrayList(); String fixedValue = null; - Date date = null; + OffsetDateTime date = null; if (currentEnum != null && !currentEnum.isEmpty()) { for (Object enumItem : currentEnum) { - date = (Date) enumItem; + date = (OffsetDateTime) enumItem; fixedValue = dateToString(p, date, iso8601Date, iso8601DateTime); fixedEnum.add(fixedValue); } @@ -251,15 +249,21 @@ public class PythonClientExperimentalCodegen extends PythonClientCodegen { // convert the example if it exists Object currentExample = p.getExample(); if (currentExample != null) { - date = (Date) currentExample; + try { + date = (OffsetDateTime) currentExample; + } catch (ClassCastException e) { + date = ((Date) currentExample).toInstant().atOffset(ZoneOffset.UTC); + LOGGER.warn("Invalid `date-time` format for value {}", currentExample); + } fixedValue = dateToString(p, date, iso8601Date, iso8601DateTime); fixedEnum.add(fixedValue); p.setExample(fixedValue); + LOGGER.warn(fixedValue); } // fix defaultObject if (defaultObject != null) { - date = (Date) defaultObject; + date = (OffsetDateTime) defaultObject; fixedValue = dateToString(p, date, iso8601Date, iso8601DateTime); p.setDefault(fixedValue); defaultObject = fixedValue; @@ -377,7 +381,7 @@ public class PythonClientExperimentalCodegen extends PythonClientCodegen { /** * Override with special post-processing for all models. - */ + */ @SuppressWarnings({"static-method", "unchecked"}) public Map postProcessAllModels(Map objs) { // loop through all models and delete ones where type!=object and the model has no validations and enums @@ -905,7 +909,7 @@ public class PythonClientExperimentalCodegen extends PythonClientCodegen { * Primitive types in the OAS specification are implemented in Python using the corresponding * Python primitive types. * Composed types (e.g. allAll, oneOf, anyOf) are represented in Python using list of types. - * + * * The caller should set the prefix and suffix arguments to empty string, except when * getTypeString invokes itself recursively. A non-empty prefix/suffix may be specified * to wrap the return value in a python dict, list or tuple. @@ -913,7 +917,7 @@ public class PythonClientExperimentalCodegen extends PythonClientCodegen { * Examples: * - "bool, date, float" The data must be a bool, date or float. * - "[bool, date]" The data must be an array, and the array items must be a bool or date. - * + * * @param p The OAS schema. * @param prefix prepended to the returned value. * @param suffix appended to the returned value. @@ -922,7 +926,6 @@ public class PythonClientExperimentalCodegen extends PythonClientCodegen { * @return a comma-separated string representation of the Python types */ private String getTypeString(Schema p, String prefix, String suffix, List referencedModelNames) { - // this is used to set dataType, which defines a python tuple of classes String fullSuffix = suffix; if (")".equals(suffix)) { fullSuffix = "," + suffix; @@ -968,7 +971,7 @@ public class PythonClientExperimentalCodegen extends PythonClientCodegen { } else { return prefix + getTypeString(inner, "[", "]", referencedModelNames) + fullSuffix; } - } + } if (ModelUtils.isFileSchema(p)) { return prefix + "file_type" + fullSuffix; } diff --git a/modules/openapi-generator/src/test/java/org/openapitools/codegen/python/PythonClientExperimentalTest.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/python/PythonClientExperimentalTest.java index c7988952b7f..8ad4990a554 100644 --- a/modules/openapi-generator/src/test/java/org/openapitools/codegen/python/PythonClientExperimentalTest.java +++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/python/PythonClientExperimentalTest.java @@ -21,8 +21,10 @@ import io.swagger.v3.oas.models.OpenAPI; import io.swagger.v3.oas.models.Operation; import io.swagger.v3.oas.models.media.*; import io.swagger.v3.parser.util.SchemaTypeUtil; +import java.time.OffsetDateTime; import org.openapitools.codegen.*; import org.openapitools.codegen.languages.PythonClientExperimentalCodegen; +import org.openapitools.codegen.utils.ModelUtils; import org.testng.Assert; import org.testng.annotations.Test; @@ -293,4 +295,14 @@ public class PythonClientExperimentalTest { Assert.assertEquals(cm.imports.size(), 0); } + @Test(description = "parse date and date-time example value") + public void parseDateAndDateTimeExamplesTest() { + final OpenAPI openAPI = TestUtils.parseFlattenSpec("src/test/resources/3_0/python-experimental/petstore-with-fake-endpoints-models-for-testing-with-http-signature.yaml"); + final DefaultCodegen codegen = new PythonClientExperimentalCodegen(); + + Schema modelSchema = ModelUtils.getSchema(openAPI, "DateTimeTest"); + String defaultValue = codegen.toDefaultValue(modelSchema); + Assert.assertEquals(defaultValue, "dateutil_parser('2010-01-01T10:10:10.000111+01:00')"); + } + } diff --git a/modules/openapi-generator/src/test/resources/3_0/python-experimental/petstore-with-fake-endpoints-models-for-testing-with-http-signature.yaml b/modules/openapi-generator/src/test/resources/3_0/python-experimental/petstore-with-fake-endpoints-models-for-testing-with-http-signature.yaml index 6fb6d09416e..c9bf37fe1a1 100644 --- a/modules/openapi-generator/src/test/resources/3_0/python-experimental/petstore-with-fake-endpoints-models-for-testing-with-http-signature.yaml +++ b/modules/openapi-generator/src/test/resources/3_0/python-experimental/petstore-with-fake-endpoints-models-for-testing-with-http-signature.yaml @@ -758,6 +758,8 @@ paths: description: None type: string format: date-time + default: '2010-02-01T10:20:10.11111+01:00' + example: '2020-02-02T20:20:20.22222Z' password: description: None type: string @@ -1202,6 +1204,7 @@ components: shipDate: type: string format: date-time + example: '2020-02-02T20:20:20.000222Z' status: type: string description: Order Status @@ -1443,7 +1446,7 @@ components: maximum: 543.2 minimum: 32.1 type: number - multipleOf: 32.5 + multipleOf: 32.5 float: type: number format: float @@ -1466,9 +1469,11 @@ components: date: type: string format: date + example: '2020-02-02' dateTime: type: string format: date-time + example: '2007-12-03T10:15:30+01:00' uuid: type: string format: uuid @@ -1969,7 +1974,7 @@ components: # Here the additional properties are specified using a referenced schema. # This is just to validate the generated code works when using $ref # under 'additionalProperties'. - $ref: '#/components/schemas/fruit' + $ref: '#/components/schemas/fruit' Shape: oneOf: - $ref: '#/components/schemas/Triangle' @@ -2069,3 +2074,8 @@ components: properties: name: type: string + DateTimeTest: + type: string + default: '2010-01-01T10:10:10.000111+01:00' + example: '2010-01-01T10:10:10.000111+01:00' + format: date-time diff --git a/samples/openapi3/client/petstore/python-experimental/docs/InlineObject3.md b/samples/openapi3/client/petstore/python-experimental/docs/InlineObject3.md index 050d635dd25..69815c5e63c 100644 --- a/samples/openapi3/client/petstore/python-experimental/docs/InlineObject3.md +++ b/samples/openapi3/client/petstore/python-experimental/docs/InlineObject3.md @@ -14,7 +14,7 @@ Name | Type | Description | Notes **string** | **str** | None | [optional] **binary** | **file_type** | None | [optional] **date** | **date** | None | [optional] -**date_time** | **datetime** | None | [optional] +**date_time** | **datetime** | None | [optional] if omitted the server will use the default value of dateutil_parser('2010-02-01T10:20:10.11111+01:00') **password** | **str** | None | [optional] **callback** | **str** | None | [optional] diff --git a/samples/openapi3/client/petstore/python-experimental/petstore_api/model/inline_object3.py b/samples/openapi3/client/petstore/python-experimental/petstore_api/model/inline_object3.py index ff595ddaa1f..01c49c375e7 100644 --- a/samples/openapi3/client/petstore/python-experimental/petstore_api/model/inline_object3.py +++ b/samples/openapi3/client/petstore/python-experimental/petstore_api/model/inline_object3.py @@ -210,7 +210,7 @@ class InlineObject3(ModelNormal): string (str): None. [optional] # noqa: E501 binary (file_type): None. [optional] # noqa: E501 date (date): None. [optional] # noqa: E501 - date_time (datetime): None. [optional] # noqa: E501 + date_time (datetime): None. [optional] if omitted the server will use the default value of dateutil_parser('2010-02-01T10:20:10.11111+01:00') # noqa: E501 password (str): None. [optional] # noqa: E501 callback (str): None. [optional] # noqa: E501 """