[python] Fix date-time parsing (#6458)

This commit is contained in:
Jiri Kuncar 2020-06-04 03:26:30 +02:00 committed by GitHub
parent e2e3405689
commit d07f459ce3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 49 additions and 24 deletions

View File

@ -722,7 +722,7 @@ public class PythonClientCodegen extends DefaultCodegen implements CodegenConfig
} }
// correct "'"s into "'"s after toString() // 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(); example = (String) schema.getDefault();
} }

View File

@ -38,8 +38,9 @@ import org.openapitools.codegen.meta.Stability;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import java.text.DateFormat; import java.time.OffsetDateTime;
import java.text.SimpleDateFormat; import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
import java.io.File; import java.io.File;
import java.util.*; import java.util.*;
import java.util.regex.Pattern; import java.util.regex.Pattern;
@ -196,15 +197,15 @@ public class PythonClientExperimentalCodegen extends PythonClientCodegen {
return "python-experimental"; 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 // converts a date into a date or date-time python string
if (!(ModelUtils.isDateSchema(p) || ModelUtils.isDateTimeSchema(p))) { if (!(ModelUtils.isDateSchema(p) || ModelUtils.isDateTimeSchema(p))) {
throw new RuntimeException("passed schema must be of type Date or DateTime"); throw new RuntimeException("passed schema must be of type Date or DateTime");
} }
if (ModelUtils.isDateSchema(p)) { 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 // convert datetime and date enums if they exist
DateFormat iso8601Date = new SimpleDateFormat("yyyy-MM-dd", Locale.ROOT); DateTimeFormatter iso8601Date = DateTimeFormatter.ISO_DATE;
DateFormat iso8601DateTime = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSXXX", Locale.ROOT); DateTimeFormatter iso8601DateTime = DateTimeFormatter.ISO_DATE_TIME;
TimeZone utc = TimeZone.getTimeZone("UTC");
iso8601Date.setTimeZone(utc);
iso8601DateTime.setTimeZone(utc);
if (ModelUtils.isDateSchema(p) || ModelUtils.isDateTimeSchema(p)) { if (ModelUtils.isDateSchema(p) || ModelUtils.isDateTimeSchema(p)) {
List<Object> currentEnum = p.getEnum(); List<Object> currentEnum = p.getEnum();
List<String> fixedEnum = new ArrayList<String>(); List<String> fixedEnum = new ArrayList<String>();
String fixedValue = null; String fixedValue = null;
Date date = null; OffsetDateTime date = null;
if (currentEnum != null && !currentEnum.isEmpty()) { if (currentEnum != null && !currentEnum.isEmpty()) {
for (Object enumItem : currentEnum) { for (Object enumItem : currentEnum) {
date = (Date) enumItem; date = (OffsetDateTime) enumItem;
fixedValue = dateToString(p, date, iso8601Date, iso8601DateTime); fixedValue = dateToString(p, date, iso8601Date, iso8601DateTime);
fixedEnum.add(fixedValue); fixedEnum.add(fixedValue);
} }
@ -251,15 +249,21 @@ public class PythonClientExperimentalCodegen extends PythonClientCodegen {
// convert the example if it exists // convert the example if it exists
Object currentExample = p.getExample(); Object currentExample = p.getExample();
if (currentExample != null) { 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); fixedValue = dateToString(p, date, iso8601Date, iso8601DateTime);
fixedEnum.add(fixedValue); fixedEnum.add(fixedValue);
p.setExample(fixedValue); p.setExample(fixedValue);
LOGGER.warn(fixedValue);
} }
// fix defaultObject // fix defaultObject
if (defaultObject != null) { if (defaultObject != null) {
date = (Date) defaultObject; date = (OffsetDateTime) defaultObject;
fixedValue = dateToString(p, date, iso8601Date, iso8601DateTime); fixedValue = dateToString(p, date, iso8601Date, iso8601DateTime);
p.setDefault(fixedValue); p.setDefault(fixedValue);
defaultObject = fixedValue; defaultObject = fixedValue;
@ -377,7 +381,7 @@ public class PythonClientExperimentalCodegen extends PythonClientCodegen {
/** /**
* Override with special post-processing for all models. * Override with special post-processing for all models.
*/ */
@SuppressWarnings({"static-method", "unchecked"}) @SuppressWarnings({"static-method", "unchecked"})
public Map<String, Object> postProcessAllModels(Map<String, Object> objs) { public Map<String, Object> postProcessAllModels(Map<String, Object> objs) {
// loop through all models and delete ones where type!=object and the model has no validations and enums // 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 * Primitive types in the OAS specification are implemented in Python using the corresponding
* Python primitive types. * Python primitive types.
* Composed types (e.g. allAll, oneOf, anyOf) are represented in Python using list of 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 * 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 * getTypeString invokes itself recursively. A non-empty prefix/suffix may be specified
* to wrap the return value in a python dict, list or tuple. * to wrap the return value in a python dict, list or tuple.
@ -913,7 +917,7 @@ public class PythonClientExperimentalCodegen extends PythonClientCodegen {
* Examples: * Examples:
* - "bool, date, float" The data must be a bool, date or float. * - "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. * - "[bool, date]" The data must be an array, and the array items must be a bool or date.
* *
* @param p The OAS schema. * @param p The OAS schema.
* @param prefix prepended to the returned value. * @param prefix prepended to the returned value.
* @param suffix appended 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 * @return a comma-separated string representation of the Python types
*/ */
private String getTypeString(Schema p, String prefix, String suffix, List<String> referencedModelNames) { private String getTypeString(Schema p, String prefix, String suffix, List<String> referencedModelNames) {
// this is used to set dataType, which defines a python tuple of classes
String fullSuffix = suffix; String fullSuffix = suffix;
if (")".equals(suffix)) { if (")".equals(suffix)) {
fullSuffix = "," + suffix; fullSuffix = "," + suffix;
@ -968,7 +971,7 @@ public class PythonClientExperimentalCodegen extends PythonClientCodegen {
} else { } else {
return prefix + getTypeString(inner, "[", "]", referencedModelNames) + fullSuffix; return prefix + getTypeString(inner, "[", "]", referencedModelNames) + fullSuffix;
} }
} }
if (ModelUtils.isFileSchema(p)) { if (ModelUtils.isFileSchema(p)) {
return prefix + "file_type" + fullSuffix; return prefix + "file_type" + fullSuffix;
} }

View File

@ -21,8 +21,10 @@ import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.Operation; import io.swagger.v3.oas.models.Operation;
import io.swagger.v3.oas.models.media.*; import io.swagger.v3.oas.models.media.*;
import io.swagger.v3.parser.util.SchemaTypeUtil; import io.swagger.v3.parser.util.SchemaTypeUtil;
import java.time.OffsetDateTime;
import org.openapitools.codegen.*; import org.openapitools.codegen.*;
import org.openapitools.codegen.languages.PythonClientExperimentalCodegen; import org.openapitools.codegen.languages.PythonClientExperimentalCodegen;
import org.openapitools.codegen.utils.ModelUtils;
import org.testng.Assert; import org.testng.Assert;
import org.testng.annotations.Test; import org.testng.annotations.Test;
@ -293,4 +295,14 @@ public class PythonClientExperimentalTest {
Assert.assertEquals(cm.imports.size(), 0); 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')");
}
} }

View File

@ -758,6 +758,8 @@ paths:
description: None description: None
type: string type: string
format: date-time format: date-time
default: '2010-02-01T10:20:10.11111+01:00'
example: '2020-02-02T20:20:20.22222Z'
password: password:
description: None description: None
type: string type: string
@ -1202,6 +1204,7 @@ components:
shipDate: shipDate:
type: string type: string
format: date-time format: date-time
example: '2020-02-02T20:20:20.000222Z'
status: status:
type: string type: string
description: Order Status description: Order Status
@ -1443,7 +1446,7 @@ components:
maximum: 543.2 maximum: 543.2
minimum: 32.1 minimum: 32.1
type: number type: number
multipleOf: 32.5 multipleOf: 32.5
float: float:
type: number type: number
format: float format: float
@ -1466,9 +1469,11 @@ components:
date: date:
type: string type: string
format: date format: date
example: '2020-02-02'
dateTime: dateTime:
type: string type: string
format: date-time format: date-time
example: '2007-12-03T10:15:30+01:00'
uuid: uuid:
type: string type: string
format: uuid format: uuid
@ -1969,7 +1974,7 @@ components:
# Here the additional properties are specified using a referenced schema. # Here the additional properties are specified using a referenced schema.
# This is just to validate the generated code works when using $ref # This is just to validate the generated code works when using $ref
# under 'additionalProperties'. # under 'additionalProperties'.
$ref: '#/components/schemas/fruit' $ref: '#/components/schemas/fruit'
Shape: Shape:
oneOf: oneOf:
- $ref: '#/components/schemas/Triangle' - $ref: '#/components/schemas/Triangle'
@ -2069,3 +2074,8 @@ components:
properties: properties:
name: name:
type: string 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

View File

@ -14,7 +14,7 @@ Name | Type | Description | Notes
**string** | **str** | None | [optional] **string** | **str** | None | [optional]
**binary** | **file_type** | None | [optional] **binary** | **file_type** | None | [optional]
**date** | **date** | 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] **password** | **str** | None | [optional]
**callback** | **str** | None | [optional] **callback** | **str** | None | [optional]

View File

@ -210,7 +210,7 @@ class InlineObject3(ModelNormal):
string (str): None. [optional] # noqa: E501 string (str): None. [optional] # noqa: E501
binary (file_type): None. [optional] # noqa: E501 binary (file_type): None. [optional] # noqa: E501
date (date): 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 password (str): None. [optional] # noqa: E501
callback (str): None. [optional] # noqa: E501 callback (str): None. [optional] # noqa: E501
""" """