Python-exp remove codegemodel mutation, allow mixed OneOf types (#6797)

* Stops converting primitive models into object models, adds ComposedSchemas with mixed type

* Samples update for python-exp
This commit is contained in:
Justin Black
2020-07-18 10:13:22 -07:00
committed by GitHub
parent 44d3f717f8
commit ed84280108
52 changed files with 1883 additions and 504 deletions

View File

@@ -1960,10 +1960,10 @@ public class DefaultCodegen implements CodegenConfig {
/** /**
* Return a string representation of the schema type, resolving aliasing and references if necessary. * Return a string representation of the schema type, resolving aliasing and references if necessary.
* *
* @param schema * @param schema input
* @return the string representation of the schema type. * @return the string representation of the schema type.
*/ */
private String getSingleSchemaType(Schema schema) { protected String getSingleSchemaType(Schema schema) {
Schema unaliasSchema = ModelUtils.unaliasSchema(this.openAPI, schema, importMapping); Schema unaliasSchema = ModelUtils.unaliasSchema(this.openAPI, schema, importMapping);
if (StringUtils.isNotBlank(unaliasSchema.get$ref())) { // reference to another definition/schema if (StringUtils.isNotBlank(unaliasSchema.get$ref())) { // reference to another definition/schema

View File

@@ -423,6 +423,7 @@ public class PythonClientExperimentalCodegen extends PythonClientCodegen {
CodegenProperty modelProperty = fromProperty("value", modelSchema); CodegenProperty modelProperty = fromProperty("value", modelSchema);
// import complex type from additional properties // import complex type from additional properties
// TODO can I remove this? DOes it exist in defaultcodegen?
if (cm.additionalPropertiesType != null && modelProperty.items != null && modelProperty.items.complexType != null) { if (cm.additionalPropertiesType != null && modelProperty.items != null && modelProperty.items.complexType != null) {
cm.imports.add(modelProperty.items.complexType); cm.imports.add(modelProperty.items.complexType);
} }
@@ -800,9 +801,11 @@ public class PythonClientExperimentalCodegen extends PythonClientCodegen {
public CodegenModel fromModel(String name, Schema schema) { public CodegenModel fromModel(String name, Schema schema) {
// we have a custom version of this function so we can produce // we have a custom version of this function so we can produce
// models for components whose type != object and which have validations and enums // models for components whose type != object and which have validations and enums
// this ensures that endpoint (operation) responses with validations and enums // this ensures that:
// will generate models, and when those endpoint responses are received in python // - endpoint (operation) responses with validations and type!=(object or array)
// the response is cast as a model, and the model will validate the response using the enums and validations // - oneOf $ref components with validations and type!=(object or array)
// when endpoints receive payloads of these models
// that they will be converted into instances of these models
Map<String, String> propertyToModelName = new HashMap<String, String>(); Map<String, String> propertyToModelName = new HashMap<String, String>();
Map<String, Schema> propertiesMap = schema.getProperties(); Map<String, Schema> propertiesMap = schema.getProperties();
if (propertiesMap != null) { if (propertiesMap != null) {
@@ -820,7 +823,7 @@ public class PythonClientExperimentalCodegen extends PythonClientCodegen {
continue; continue;
} }
CodegenProperty modelProperty = fromProperty("_fake_name", refSchema); CodegenProperty modelProperty = fromProperty("_fake_name", refSchema);
if (modelProperty.isEnum == false && modelProperty.hasValidation == false) { if (modelProperty.isEnum == true || modelProperty.hasValidation == false) {
continue; continue;
} }
String modelName = ModelUtils.getSimpleRef(ref); String modelName = ModelUtils.getSimpleRef(ref);
@@ -828,37 +831,111 @@ public class PythonClientExperimentalCodegen extends PythonClientCodegen {
} }
} }
CodegenModel result = super.fromModel(name, schema); CodegenModel result = super.fromModel(name, schema);
// have oneOf point to the correct model
if (ModelUtils.isComposedSchema(schema)) {
ComposedSchema cs = (ComposedSchema) schema;
Map<String, Integer> importCounts = new HashMap<String, Integer>();
List<Schema> oneOfSchemas = cs.getOneOf();
if (oneOfSchemas != null) {
for (int i = 0; i < oneOfSchemas.size(); i++) {
Schema oneOfSchema = oneOfSchemas.get(i);
String languageType = getTypeDeclaration(oneOfSchema);
String ref = oneOfSchema.get$ref();
if (ref == null) {
Integer currVal = importCounts.getOrDefault(languageType, 0);
importCounts.put(languageType, currVal+1);
continue;
}
Schema refSchema = ModelUtils.getReferencedSchema(this.openAPI, oneOfSchema);
String refType = refSchema.getType();
if (refType == null || refType.equals("object")) {
Integer currVal = importCounts.getOrDefault(languageType, 0);
importCounts.put(languageType, currVal+1);
continue;
}
CodegenProperty modelProperty = fromProperty("_oneOfSchema", refSchema);
if (modelProperty.isEnum == true) {
Integer currVal = importCounts.getOrDefault(languageType, 0);
importCounts.put(languageType, currVal+1);
continue;
}
languageType = getTypeDeclaration(refSchema);
if (modelProperty.hasValidation == false) {
Integer currVal = importCounts.getOrDefault(languageType, 0);
importCounts.put(languageType, currVal+1);
continue;
}
Integer currVal = importCounts.getOrDefault(languageType, 0);
importCounts.put(languageType, currVal);
String modelName = toModelName(ModelUtils.getSimpleRef(ref));
result.imports.add(modelName);
result.oneOf.add(modelName);
currVal = importCounts.getOrDefault(modelName, 0);
importCounts.put(modelName, currVal+1);
}
}
for (Map.Entry<String, Integer> entry : importCounts.entrySet()) {
String importName = entry.getKey();
Integer importCount = entry.getValue();
if (importCount == 0) {
result.oneOf.remove(importName);
}
}
}
// use this to store the model name like Cat // use this to store the model name like Cat
// we can't use result.name because that is used to lookup models in the spec // we can't use result.name because that is used to lookup models in the spec
// we can't use result.classname because that stores cat.Cat // we can't use result.classname because that stores cat.Cat
// we can't use result.classVarName because that stores the variable for making example instances // we can't use result.classVarName because that stores the variable for making example instances
result.unescapedDescription = simpleModelName(name); result.unescapedDescription = simpleModelName(name);
// make non-object type models have one property so we can use it to store enums and validations // this block handles models which have the python base class ModelSimple
if (result.isAlias || result.isEnum || result.isArrayModel) { // which are responsible for storing validations, enums, and an unnamed value
Schema modelSchema = ModelUtils.getSchema(this.openAPI, result.name); Schema modelSchema = ModelUtils.getSchema(this.openAPI, result.name);
CodegenProperty modelProperty = fromProperty("value", modelSchema); CodegenProperty modelProperty = fromProperty("_value", modelSchema);
if (modelProperty.isEnum == true || modelProperty.hasValidation == true || result.isArrayModel) {
// these models are non-object models with enums and/or validations // import complex type from additional properties
// add a single property to the model so we can have a way to access validations if (result.additionalPropertiesType != null && modelProperty.items != null && modelProperty.items.complexType != null) {
result.isAlias = true; result.imports.add(modelProperty.items.complexType);
modelProperty.required = true;
List<CodegenProperty> theProperties = Arrays.asList(modelProperty);
result.setAllVars(theProperties);
result.setVars(theProperties);
result.setRequiredVars(theProperties);
// post process model properties
if (result.vars != null) {
for (CodegenProperty prop : result.vars) {
postProcessModelProperty(result, prop);
}
}
}
} }
// set regex values, before it was only done on model.vars Boolean isPythonModelSimpleModel = (result.isEnum || result.isArrayModel || result.isAlias && modelProperty.hasValidation);
// fix all property references to non-object models, make those properties non-primitive and if (isPythonModelSimpleModel) {
// In python, classes which inherit from our ModelSimple class store one value,
// like a str, int, list and extra data about that value like validations and enums
if (result.isEnum) {
// if there is only one allowed value then we know that it should be set, so value is optional
// -> hasRequired = false
// if there are more than one allowed value then value is positional and required so
// -> hasRequired = true
ArrayList values = (ArrayList) result.allowableValues.get("values");
if (values != null && values.size() > 1) {
result.hasRequired = true;
}
if (modelProperty.defaultValue != null && result.defaultValue == null) {
result.defaultValue = modelProperty.defaultValue;
}
} else {
if (result.isArrayModel && modelProperty.dataType != null && result.dataType == null) {
// needed for array models with complex types
result.dataType = modelProperty.dataType;
result.arrayModelType = getPythonClassName(result.arrayModelType);
}
if (result.defaultValue == null) {
result.hasRequired = true;
}
}
}
// fix all property references to ModelSimple models, make those properties non-primitive and
// set their dataType and complexType to the model name, so documentation will refer to the correct model // set their dataType and complexType to the model name, so documentation will refer to the correct model
// set regex values, before it was only done on model.vars
// NOTE: this is done for models of type != object which are not enums and have validations
ArrayList<List<CodegenProperty>> listOfLists = new ArrayList<List<CodegenProperty>>(); ArrayList<List<CodegenProperty>> listOfLists = new ArrayList<List<CodegenProperty>>();
listOfLists.add(result.vars); listOfLists.add(result.vars);
listOfLists.add(result.allVars); listOfLists.add(result.allVars);
@@ -883,6 +960,7 @@ public class PythonClientExperimentalCodegen extends PythonClientCodegen {
result.imports.add(modelName); result.imports.add(modelName);
} }
} }
// if a class has a property of type self, remove the self import from imports // if a class has a property of type self, remove the self import from imports
if (result.imports.contains(result.classname)) { if (result.imports.contains(result.classname)) {
result.imports.remove(result.classname); result.imports.remove(result.classname);
@@ -895,6 +973,60 @@ public class PythonClientExperimentalCodegen extends PythonClientCodegen {
return result; return result;
} }
/**
* returns the OpenAPI type for the property. Use getAlias to handle $ref of primitive type
* We have a custom version of this function because for composed schemas we also want to return the model name
* In DefaultCodegen.java it returns a name built off of individual allOf/anyOf/oneOf which is not what
* python-experimental needs. Python-experimental needs the name of the composed schema
*
* @param schema property schema
* @return string presentation of the type
**/
@SuppressWarnings("static-method")
@Override
public String getSchemaType(Schema schema) {
if (schema instanceof ComposedSchema) { // composed schema
Schema unaliasSchema = ModelUtils.unaliasSchema(this.openAPI, schema, importMapping);
String ref = unaliasSchema.get$ref();
if (ref != null) {
String schemaName = ModelUtils.getSimpleRef(unaliasSchema.get$ref());
if (StringUtils.isNotEmpty(schemaName) && importMapping.containsKey(schemaName)) {
return schemaName;
}
return getAlias(schemaName);
} else {
// we may have be processing the component schema rather than a schema with a $ref
// to a component schema
// so loop through component schemas and use the found one's name if we match
Map<String, Schema> schemas = ModelUtils.getSchemas(openAPI);
for (String thisSchemaName : schemas.keySet()) {
Schema thisSchema = schemas.get(thisSchemaName);
if (!ModelUtils.isComposedSchema(thisSchema)) {
continue;
}
if (thisSchema == unaliasSchema) {
if (importMapping.containsKey(thisSchemaName)) {
return thisSchemaName;
}
return getAlias(thisSchemaName);
}
}
LOGGER.warn("Error obtaining the datatype from ref:" + unaliasSchema.get$ref() + ". Default to 'object'");
return "object";
}
}
String openAPIType = getSingleSchemaType(schema);
if (typeMapping.containsKey(openAPIType)) {
String type = typeMapping.get(openAPIType);
if (languageSpecificPrimitives.contains(type)) {
return type;
}
} else {
return toModelName(openAPIType);
}
return openAPIType;
}
/** /**
* Output the type declaration of the property * Output the type declaration of the property
* *

View File

@@ -33,17 +33,22 @@ from {{packageName}}.model_utils import ( # noqa: F401
{{^interfaces}} {{^interfaces}}
{{#isAlias}}
{{> python-experimental/model_templates/model_simple }}
{{/isAlias}}
{{^isAlias}}
{{#isArrayModel}} {{#isArrayModel}}
{{> python-experimental/model_templates/model_simple }} {{> python-experimental/model_templates/model_simple }}
{{/isArrayModel}} {{/isArrayModel}}
{{^isArrayModel}} {{#isEnum}}
{{> python-experimental/model_templates/model_normal }} {{> python-experimental/model_templates/model_simple }}
{{/isArrayModel}} {{/isEnum}}
{{#isAlias}}
{{> python-experimental/model_templates/model_simple }}
{{/isAlias}} {{/isAlias}}
{{^isArrayModel}}
{{^isEnum}}
{{^isAlias}}
{{> python-experimental/model_templates/model_normal }}
{{/isAlias}}
{{/isEnum}}
{{/isArrayModel}}
{{/interfaces}} {{/interfaces}}
{{#interfaces}} {{#interfaces}}
{{#-last}} {{#-last}}

View File

@@ -5,6 +5,15 @@
## Properties ## Properties
Name | Type | Description | Notes Name | Type | Description | Notes
------------ | ------------- | ------------- | ------------- ------------ | ------------- | ------------- | -------------
{{#isEnum}}
**value** | {{^arrayModelType}}**{{dataType}}**{{/arrayModelType}} | {{description}} | {{#defaultValue}}{{#hasRequired}} if omitted the server will use the default value of {{/hasRequired}}{{^hasRequired}}defaults to {{/hasRequired}}{{{.}}}{{/defaultValue}}{{#allowableValues}}{{#defaultValue}}, {{/defaultValue}} must be one of [{{#enumVars}}{{{value}}}, {{/enumVars}}]{{/allowableValues}}
{{/isEnum}}
{{#isAlias}}
**value** | {{^arrayModelType}}**{{dataType}}**{{/arrayModelType}} | {{description}} | {{#defaultValue}}{{#hasRequired}} if omitted the server will use the default value of {{/hasRequired}}{{^hasRequired}}defaults to {{/hasRequired}}{{{.}}}{{/defaultValue}}
{{/isAlias}}
{{#isArrayModel}}
**value** | {{^arrayModelType}}**{{dataType}}**{{/arrayModelType}}{{#arrayModelType}}[**{{dataType}}**]({{arrayModelType}}.md){{/arrayModelType}} | {{description}} | {{#defaultValue}}{{#hasRequired}} if omitted the server will use the default value of {{/hasRequired}}{{^hasRequired}}defaults to {{/hasRequired}}{{{.}}}{{/defaultValue}}
{{/isArrayModel}}
{{#requiredVars}} {{#requiredVars}}
{{^defaultValue}} {{^defaultValue}}
**{{name}}** | {{^complexType}}**{{dataType}}**{{/complexType}}{{#complexType}}[**{{dataType}}**]({{complexType}}.md){{/complexType}} | {{description}} | {{#isReadOnly}}[readonly] {{/isReadOnly}} **{{name}}** | {{^complexType}}**{{dataType}}**{{/complexType}}{{#complexType}}[**{{dataType}}**]({{complexType}}.md){{/complexType}} | {{description}} | {{#isReadOnly}}[readonly] {{/isReadOnly}}

View File

@@ -1,4 +1,16 @@
allowed_values = { allowed_values = {
{{#isEnum}}
('value',): {
{{#isNullable}}
'None': None,
{{/isNullable}}
{{#allowableValues}}
{{#enumVars}}
'{{name}}': {{{value}}},
{{/enumVars}}
{{/allowableValues}}
},
{{/isEnum}}
{{#requiredVars}} {{#requiredVars}}
{{#isEnum}} {{#isEnum}}
('{{name}}',): { ('{{name}}',): {
@@ -30,70 +42,24 @@
} }
validations = { validations = {
{{#isAlias}}
{{^isEnum}}
{{^isArrayModel}}
('value',): {
{{> python-experimental/model_templates/validations }}
{{/isArrayModel}}
{{/isEnum}}
{{/isAlias}}
{{#requiredVars}} {{#requiredVars}}
{{#hasValidation}} {{#hasValidation}}
('{{name}}',): { ('{{name}}',): {
{{#maxLength}} {{> python-experimental/model_templates/validations }}
'max_length': {{maxLength}},
{{/maxLength}}
{{#minLength}}
'min_length': {{minLength}},
{{/minLength}}
{{#maxItems}}
'max_items': {{maxItems}},
{{/maxItems}}
{{#minItems}}
'min_items': {{minItems}},
{{/minItems}}
{{#maximum}}
{{#exclusiveMaximum}}'exclusive_maximum'{{/exclusiveMaximum}}'inclusive_maximum'{{^exclusiveMaximum}}{{/exclusiveMaximum}}: {{maximum}},
{{/maximum}}
{{#minimum}}
{{#exclusiveMinimum}}'exclusive_minimum'{{/exclusiveMinimum}}'inclusive_minimum'{{^exclusiveMinimum}}{{/exclusiveMinimum}}: {{minimum}},
{{/minimum}}
{{#pattern}}
'regex': {
'pattern': r'{{{vendorExtensions.x-regex}}}', # noqa: E501{{#vendorExtensions.x-modifiers}}
{{#-first}}'flags': (re.{{.}}{{/-first}}{{^-first}} re.{{.}}{{/-first}}{{^-last}} | {{/-last}}{{#-last}}){{/-last}}{{/vendorExtensions.x-modifiers}}
},
{{/pattern}}
{{#multipleOf}}
'multiple_of': {{multipleOf}},
{{/multipleOf}}
},
{{/hasValidation}} {{/hasValidation}}
{{/requiredVars}} {{/requiredVars}}
{{#optionalVars}} {{#optionalVars}}
{{#hasValidation}} {{#hasValidation}}
('{{name}}',): { ('{{name}}',): {
{{#maxLength}} {{> python-experimental/model_templates/validations }}
'max_length': {{maxLength}},
{{/maxLength}}
{{#minLength}}
'min_length': {{minLength}},
{{/minLength}}
{{#maxItems}}
'max_items': {{maxItems}},
{{/maxItems}}
{{#minItems}}
'min_items': {{minItems}},
{{/minItems}}
{{#maximum}}
{{#exclusiveMaximum}}'exclusive_maximum'{{/exclusiveMaximum}}'inclusive_maximum'{{^exclusiveMaximum}}{{/exclusiveMaximum}}: {{maximum}},
{{/maximum}}
{{#minimum}}
{{#exclusiveMinimum}}'exclusive_minimum'{{/exclusiveMinimum}}'inclusive_minimum'{{^exclusiveMinimum}}{{/exclusiveMinimum}}: {{minimum}},
{{/minimum}}
{{#pattern}}
'regex': {
'pattern': r'{{{vendorExtensions.x-regex}}}', # noqa: E501{{#vendorExtensions.x-modifiers}}
{{#-first}}'flags': (re.{{.}}{{/-first}}{{^-first}} re.{{.}}{{/-first}}{{^-last}} | {{/-last}}{{#-last}}){{/-last}}{{/vendorExtensions.x-modifiers}}
},
{{/pattern}}
{{#multipleOf}}
'multiple_of': {{multipleOf}},
{{/multipleOf}}
},
{{/hasValidation}} {{/hasValidation}}
{{/optionalVars}} {{/optionalVars}}
} }
@@ -113,6 +79,15 @@
and the value is attribute type. and the value is attribute type.
""" """
return { return {
{{#isAlias}}
'value': ({{{dataType}}},),
{{/isAlias}}
{{#isEnum}}
'value': ({{{dataType}}},),
{{/isEnum}}
{{#isArrayModel}}
'value': ({{{dataType}}},),
{{/isArrayModel}}
{{#requiredVars}} {{#requiredVars}}
'{{name}}': ({{{dataType}}},), # noqa: E501 '{{name}}': ({{{dataType}}},), # noqa: E501
{{/requiredVars}} {{/requiredVars}}

View File

@@ -0,0 +1,30 @@
_check_type (bool): if True, values for parameters in openapi_types
will be type checked and a TypeError will be
raised if the wrong type is input.
Defaults to True
_path_to_item (tuple/list): This is a list of keys or values to
drill down to the model in received_data
when deserializing a response
_spec_property_naming (bool): True if the variable names in the input data
are serialized names, as specified in the OpenAPI document.
False if the variable names in the input data
are pythonic names, e.g. snake case (default)
_configuration (Configuration): the instance to use when
deserializing a file_type parameter.
If passed, type conversion is attempted
If omitted no type conversion is done.
_visited_composed_classes (tuple): This stores a tuple of
classes that we have traveled through so that
if we see that class again we will not use its
discriminator again.
When traveling through a discriminator, the
composed schema that is
is traveled through is added to this set.
For example if Animal has a discriminator
petType and we pass in "Dog", and the class Dog
allOf includes Animal, we move through Animal
once using the discriminator, and pick Dog.
Then in Dog, we will make an instance of the
Animal class but this time we won't travel
through its discriminator because we passed in
_visited_composed_classes = (Animal,)

View File

@@ -9,6 +9,9 @@
{{> python-experimental/model_templates/method_init_shared }} {{> python-experimental/model_templates/method_init_shared }}
{{#isEnum}}
self.value = value
{{/isEnum}}
{{#requiredVars}} {{#requiredVars}}
self.{{name}} = {{name}} self.{{name}} = {{name}}
{{/requiredVars}} {{/requiredVars}}

View File

@@ -19,36 +19,7 @@
{{name}} ({{{dataType}}}):{{#description}} {{description}}.{{/description}} defaults to {{{defaultValue}}}{{#allowableValues}}, must be one of [{{#enumVars}}{{{value}}}, {{/enumVars}}]{{/allowableValues}} # noqa: E501 {{name}} ({{{dataType}}}):{{#description}} {{description}}.{{/description}} defaults to {{{defaultValue}}}{{#allowableValues}}, must be one of [{{#enumVars}}{{{value}}}, {{/enumVars}}]{{/allowableValues}} # noqa: E501
{{/defaultValue}} {{/defaultValue}}
{{/requiredVars}} {{/requiredVars}}
_check_type (bool): if True, values for parameters in openapi_types {{> python-experimental/model_templates/docstring_init_required_kwargs }}
will be type checked and a TypeError will be
raised if the wrong type is input.
Defaults to True
_path_to_item (tuple/list): This is a list of keys or values to
drill down to the model in received_data
when deserializing a response
_spec_property_naming (bool): True if the variable names in the input data
are serialized names, as specified in the OpenAPI document.
False if the variable names in the input data
are pythonic names, e.g. snake case (default)
_configuration (Configuration): the instance to use when
deserializing a file_type parameter.
If passed, type conversion is attempted
If omitted no type conversion is done.
_visited_composed_classes (tuple): This stores a tuple of
classes that we have traveled through so that
if we see that class again we will not use its
discriminator again.
When traveling through a discriminator, the
composed schema that is
is traveled through is added to this set.
For example if Animal has a discriminator
petType and we pass in "Dog", and the class Dog
allOf includes Animal, we move through Animal
once using the discriminator, and pick Dog.
Then in Dog, we will make an instance of the
Animal class but this time we won't travel
through its discriminator because we passed in
_visited_composed_classes = (Animal,)
{{#optionalVars}} {{#optionalVars}}
{{name}} ({{{dataType}}}):{{#description}} {{description}}.{{/description}} [optional]{{#defaultValue}} if omitted the server will use the default value of {{{defaultValue}}}{{/defaultValue}} # noqa: E501 {{name}} ({{{dataType}}}):{{#description}} {{description}}.{{/description}} [optional]{{#defaultValue}} if omitted the server will use the default value of {{{defaultValue}}}{{/defaultValue}} # noqa: E501
{{/optionalVars}} {{/optionalVars}}

View File

@@ -0,0 +1,66 @@
required_properties = set([
'_data_store',
'_check_type',
'_spec_property_naming',
'_path_to_item',
'_configuration',
'_visited_composed_classes',
])
@convert_js_args_to_python_args
def __init__(self{{#hasRequired}}, value{{/hasRequired}}, *args, **kwargs):
"""{{classname}} - a model defined in OpenAPI
{{#hasRequired}}
Args:
value ({{{dataType}}}):{{#description}} {{description}}.{{/description}}{{#defaultValue}} if omitted the server will use the default value of {{{defaultValue}}}{{/defaultValue}}{{#allowableValues}}, must be one of [{{#enumVars}}{{{value}}}, {{/enumVars}}]{{/allowableValues}} # noqa: E501
{{/hasRequired}}
Keyword Args:
{{^hasRequired}}
value ({{{dataType}}}):{{#description}} {{description}}.{{/description}}{{#defaultValue}} defaults to {{{defaultValue}}}{{/defaultValue}}{{#allowableValues}}, must be one of [{{#enumVars}}{{{value}}}, {{/enumVars}}]{{/allowableValues}} # noqa: E501
{{/hasRequired}}
{{> python-experimental/model_templates/docstring_init_required_kwargs }}
"""
{{^hasRequired}}
if 'value' in kwargs:
value = kwargs.pop('value')
elif args:
args = list(args)
value = args.pop(0)
else:
value = {{{defaultValue}}}
{{/hasRequired}}
_check_type = kwargs.pop('_check_type', True)
_spec_property_naming = kwargs.pop('_spec_property_naming', False)
_path_to_item = kwargs.pop('_path_to_item', ())
_configuration = kwargs.pop('_configuration', None)
_visited_composed_classes = kwargs.pop('_visited_composed_classes', ())
if args:
raise ApiTypeError(
"Invalid positional arguments=%s passed to %s. Remove those invalid positional arguments." % (
args,
self.__class__.__name__,
),
path_to_item=_path_to_item,
valid_classes=(self.__class__,),
)
self._data_store = {}
self._check_type = _check_type
self._spec_property_naming = _spec_property_naming
self._path_to_item = _path_to_item
self._configuration = _configuration
self._visited_composed_classes = _visited_composed_classes + (self.__class__,)
self.value = value
if kwargs:
raise ApiTypeError(
"Invalid named arguments=%s passed to %s. Remove those invalid named arguments." % (
kwargs,
self.__class__.__name__,
),
path_to_item=_path_to_item,
valid_classes=(self.__class__,),
)

View File

@@ -15,4 +15,4 @@ class {{unescapedDescription}}(ModelSimple):
_composed_schemas = None _composed_schemas = None
{{> python-experimental/model_templates/method_init_normal}} {{> python-experimental/model_templates/method_init_simple}}

View File

@@ -0,0 +1,28 @@
{{#maxLength}}
'max_length': {{maxLength}},
{{/maxLength}}
{{#minLength}}
'min_length': {{minLength}},
{{/minLength}}
{{#maxItems}}
'max_items': {{maxItems}},
{{/maxItems}}
{{#minItems}}
'min_items': {{minItems}},
{{/minItems}}
{{#maximum}}
{{#exclusiveMaximum}}'exclusive_maximum'{{/exclusiveMaximum}}'inclusive_maximum'{{^exclusiveMaximum}}{{/exclusiveMaximum}}: {{maximum}},
{{/maximum}}
{{#minimum}}
{{#exclusiveMinimum}}'exclusive_minimum'{{/exclusiveMinimum}}'inclusive_minimum'{{^exclusiveMinimum}}{{/exclusiveMinimum}}: {{minimum}},
{{/minimum}}
{{#pattern}}
'regex': {
'pattern': r'{{{vendorExtensions.x-regex}}}', # noqa: E501{{#vendorExtensions.x-modifiers}}
{{#-first}}'flags': (re.{{.}}{{/-first}}{{^-first}} re.{{.}}{{/-first}}{{^-last}} | {{/-last}}{{#-last}}){{/-last}}{{/vendorExtensions.x-modifiers}}
},
{{/pattern}}
{{#multipleOf}}
'multiple_of': {{multipleOf}},
{{/multipleOf}}
},

View File

@@ -54,6 +54,58 @@ class cached_property(object):
return result return result
PRIMITIVE_TYPES = (list, float, int, bool, datetime, date, str, file_type)
def allows_single_value_input(cls):
"""
This function returns True if the input composed schema model or any
descendant model allows a value only input
This is true for cases where oneOf contains items like:
oneOf:
- float
- NumberWithValidation
- StringEnum
- ArrayModel
- null
TODO: lru_cache this
"""
if (
issubclass(cls, ModelSimple) or
cls in PRIMITIVE_TYPES
):
return True
elif issubclass(cls, ModelComposed):
if not cls._composed_schemas['oneOf']:
return False
return any(allows_single_value_input(c) for c in cls._composed_schemas['oneOf'])
return False
def composed_model_input_classes(cls):
"""
This function returns a list of the possible models that can be accepted as
inputs.
TODO: lru_cache this
"""
if issubclass(cls, ModelSimple) or cls in PRIMITIVE_TYPES:
return [cls]
elif issubclass(cls, ModelNormal):
if cls.discriminator is None:
return [cls]
else:
return get_discriminated_classes(cls)
elif issubclass(cls, ModelComposed):
if not cls._composed_schemas['oneOf']:
return []
if cls.discriminator is None:
input_classes = []
for c in cls._composed_schemas['oneOf']:
input_classes.extend(composed_model_input_classes(c))
return input_classes
else:
return get_discriminated_classes(cls)
return []
class OpenApiModel(object): class OpenApiModel(object):
"""The base class for all OpenAPIModels""" """The base class for all OpenAPIModels"""
@@ -66,10 +118,18 @@ class OpenApiModel(object):
# pick a new schema/class to instantiate because a discriminator # pick a new schema/class to instantiate because a discriminator
# propertyName value was passed in # propertyName value was passed in
if len(args) == 1 and args[0] is None and is_type_nullable(cls): if len(args) == 1:
arg = args[0]
if arg is None and is_type_nullable(cls):
# The input data is the 'null' value and the type is nullable. # The input data is the 'null' value and the type is nullable.
return None return None
if issubclass(cls, ModelComposed) and allows_single_value_input(cls):
model_kwargs = {}
oneof_instance = get_oneof_instance(cls, model_kwargs, kwargs, model_arg=arg)
return oneof_instance
visited_composed_classes = kwargs.get('_visited_composed_classes', ()) visited_composed_classes = kwargs.get('_visited_composed_classes', ())
if ( if (
cls.discriminator is None or cls.discriminator is None or
@@ -241,6 +301,10 @@ UPCONVERSION_TYPE_PAIRS = (
(int, float), # A float may be serialized as an integer, e.g. '3' is a valid serialized float. (int, float), # A float may be serialized as an integer, e.g. '3' is a valid serialized float.
(list, ModelComposed), (list, ModelComposed),
(dict, ModelComposed), (dict, ModelComposed),
(str, ModelComposed),
(int, ModelComposed),
(float, ModelComposed),
(list, ModelComposed),
(list, ModelNormal), (list, ModelNormal),
(dict, ModelNormal), (dict, ModelNormal),
(str, ModelSimple), (str, ModelSimple),
@@ -619,20 +683,53 @@ def remove_uncoercible(required_types_classes, current_item, spec_property_namin
results_classes.append(required_type_class) results_classes.append(required_type_class)
return results_classes return results_classes
def get_discriminated_classes(cls):
"""
Returns all the classes that a discriminator converts to
TODO: lru_cache this
"""
possible_classes = []
key = list(cls.discriminator.keys())[0]
if is_type_nullable(cls):
possible_classes.append(cls)
for discr_cls in cls.discriminator[key].values():
if hasattr(discr_cls, 'discriminator') and discr_cls.discriminator is not None:
possible_classes.extend(get_discriminated_classes(discr_cls))
else:
possible_classes.append(discr_cls)
return possible_classes
def get_required_type_classes(required_types_mixed):
def get_possible_classes(cls, from_server_context):
# TODO: lru_cache this
possible_classes = [cls]
if from_server_context:
return possible_classes
if hasattr(cls, 'discriminator') and cls.discriminator is not None:
possible_classes = []
possible_classes.extend(get_discriminated_classes(cls))
elif issubclass(cls, ModelComposed):
possible_classes.extend(composed_model_input_classes(cls))
return possible_classes
def get_required_type_classes(required_types_mixed, spec_property_naming):
"""Converts the tuple required_types into a tuple and a dict described """Converts the tuple required_types into a tuple and a dict described
below below
Args: Args:
required_types_mixed (tuple/list): will contain either classes or required_types_mixed (tuple/list): will contain either classes or
instance of list or dict instance of list or dict
spec_property_naming (bool): if True these values came from the
server, and we use the data types in our endpoints.
If False, we are client side and we need to include
oneOf and discriminator classes inside the data types in our endpoints
Returns: Returns:
(valid_classes, dict_valid_class_to_child_types_mixed): (valid_classes, dict_valid_class_to_child_types_mixed):
valid_classes (tuple): the valid classes that the current item valid_classes (tuple): the valid classes that the current item
should be should be
dict_valid_class_to_child_types_mixed (doct): dict_valid_class_to_child_types_mixed (dict):
valid_class (class): this is the key valid_class (class): this is the key
child_types_mixed (list/dict/tuple): describes the valid child child_types_mixed (list/dict/tuple): describes the valid child
types types
@@ -650,7 +747,7 @@ def get_required_type_classes(required_types_mixed):
valid_classes.append(dict) valid_classes.append(dict)
child_req_types_by_current_type[dict] = required_type[str] child_req_types_by_current_type[dict] = required_type[str]
else: else:
valid_classes.append(required_type) valid_classes.extend(get_possible_classes(required_type, spec_property_naming))
return tuple(valid_classes), child_req_types_by_current_type return tuple(valid_classes), child_req_types_by_current_type
@@ -803,7 +900,7 @@ def deserialize_model(model_data, model_class, path_to_item, check_type,
"""Deserializes model_data to model instance. """Deserializes model_data to model instance.
Args: Args:
model_data (list/dict): data to instantiate the model model_data (int/str/float/bool/none_type/list/dict): data to instantiate the model
model_class (OpenApiModel): the model class model_class (OpenApiModel): the model class
path_to_item (list): path to the model in the received data path_to_item (list): path to the model in the received data
check_type (bool): whether to check the data tupe for the values in check_type (bool): whether to check the data tupe for the values in
@@ -829,14 +926,14 @@ def deserialize_model(model_data, model_class, path_to_item, check_type,
_spec_property_naming=spec_property_naming) _spec_property_naming=spec_property_naming)
if issubclass(model_class, ModelSimple): if issubclass(model_class, ModelSimple):
instance = model_class(value=model_data, **kw_args) return model_class(model_data, **kw_args)
return instance elif isinstance(model_data, list):
if isinstance(model_data, list): return model_class(*model_data, **kw_args)
instance = model_class(*model_data, **kw_args)
if isinstance(model_data, dict): if isinstance(model_data, dict):
kw_args.update(model_data) kw_args.update(model_data)
instance = model_class(**kw_args) return model_class(**kw_args)
return instance elif isinstance(model_data, PRIMITIVE_TYPES):
return model_class(model_data, **kw_args)
def deserialize_file(response_data, configuration, content_disposition=None): def deserialize_file(response_data, configuration, content_disposition=None):
@@ -1019,7 +1116,7 @@ def validate_and_convert_types(input_value, required_types_mixed, path_to_item,
Raises: Raises:
ApiTypeError ApiTypeError
""" """
results = get_required_type_classes(required_types_mixed) results = get_required_type_classes(required_types_mixed, spec_property_naming)
valid_classes, child_req_types_by_current_type = results valid_classes, child_req_types_by_current_type = results
input_class_simple = get_simple_class(input_value) input_class_simple = get_simple_class(input_value)
@@ -1256,7 +1353,7 @@ def get_allof_instances(self, model_args, constant_args):
return composed_instances return composed_instances
def get_oneof_instance(self, model_args, constant_args): def get_oneof_instance(cls, model_kwargs, constant_kwargs, model_arg=None):
""" """
Find the oneOf schema that matches the input data (e.g. payload). Find the oneOf schema that matches the input data (e.g. payload).
If exactly one schema matches the input data, an instance of that schema If exactly one schema matches the input data, an instance of that schema
@@ -1264,25 +1361,33 @@ def get_oneof_instance(self, model_args, constant_args):
If zero or more than one schema match the input data, an exception is raised. If zero or more than one schema match the input data, an exception is raised.
In OAS 3.x, the payload MUST, by validation, match exactly one of the In OAS 3.x, the payload MUST, by validation, match exactly one of the
schemas described by oneOf. schemas described by oneOf.
Args: Args:
self: the class we are handling cls: the class we are handling
model_args (dict): var_name to var_value model_kwargs (dict): var_name to var_value
The input data, e.g. the payload that must match a oneOf schema The input data, e.g. the payload that must match a oneOf schema
in the OpenAPI document. in the OpenAPI document.
constant_args (dict): var_name to var_value constant_kwargs (dict): var_name to var_value
args that every model requires, including configuration, server args that every model requires, including configuration, server
and path to item. and path to item.
Kwargs:
model_arg: (int, float, bool, str, date, datetime, ModelSimple, None):
the value to assign to a primitive class or ModelSimple class
Notes:
- this is only passed in when oneOf includes types which are not object
- None is used to suppress handling of model_arg, nullable models are handled in __new__
Returns Returns
oneof_instance (instance) oneof_instance (instance)
""" """
if len(self._composed_schemas['oneOf']) == 0: if len(cls._composed_schemas['oneOf']) == 0:
return None return None
oneof_instances = [] oneof_instances = []
# Iterate over each oneOf schema and determine if the input data # Iterate over each oneOf schema and determine if the input data
# matches the oneOf schemas. # matches the oneOf schemas.
for oneof_class in self._composed_schemas['oneOf']: for oneof_class in cls._composed_schemas['oneOf']:
# The composed oneOf schema allows the 'null' type and the input data # The composed oneOf schema allows the 'null' type and the input data
# is the null value. This is a OAS >= 3.1 feature. # is the null value. This is a OAS >= 3.1 feature.
if oneof_class is none_type: if oneof_class is none_type:
@@ -1290,9 +1395,12 @@ def get_oneof_instance(self, model_args, constant_args):
# none_type deserialization is handled in the __new__ method # none_type deserialization is handled in the __new__ method
continue continue
single_value_input = allows_single_value_input(oneof_class)
if not single_value_input:
# transform js keys from input data to python keys in fixed_model_args # transform js keys from input data to python keys in fixed_model_args
fixed_model_args = change_keys_js_to_python( fixed_model_args = change_keys_js_to_python(
model_args, oneof_class) model_kwargs, oneof_class)
# Extract a dict with the properties that are declared in the oneOf schema. # Extract a dict with the properties that are declared in the oneOf schema.
# Undeclared properties (e.g. properties that are allowed because of the # Undeclared properties (e.g. properties that are allowed because of the
@@ -1309,9 +1417,23 @@ def get_oneof_instance(self, model_args, constant_args):
continue continue
# and use it to make the instance # and use it to make the instance
kwargs.update(constant_args) kwargs.update(constant_kwargs)
try: try:
if not single_value_input:
oneof_instance = oneof_class(**kwargs) oneof_instance = oneof_class(**kwargs)
else:
if issubclass(oneof_class, ModelSimple):
oneof_instance = oneof_class(model_arg, **constant_kwargs)
elif oneof_class in PRIMITIVE_TYPES:
oneof_instance = validate_and_convert_types(
model_arg,
(oneof_class,),
constant_kwargs['_path_to_item'],
constant_kwargs['_spec_property_naming'],
constant_kwargs['_check_type'],
configuration=constant_kwargs['_configuration']
)
oneof_instances.append(oneof_instance) oneof_instances.append(oneof_instance)
except Exception: except Exception:
pass pass
@@ -1319,13 +1441,13 @@ def get_oneof_instance(self, model_args, constant_args):
raise ApiValueError( raise ApiValueError(
"Invalid inputs given to generate an instance of %s. None " "Invalid inputs given to generate an instance of %s. None "
"of the oneOf schemas matched the input data." % "of the oneOf schemas matched the input data." %
self.__class__.__name__ cls.__name__
) )
elif len(oneof_instances) > 1: elif len(oneof_instances) > 1:
raise ApiValueError( raise ApiValueError(
"Invalid inputs given to generate an instance of %s. Multiple " "Invalid inputs given to generate an instance of %s. Multiple "
"oneOf schemas matched the inputs, but a max of one is allowed." % "oneOf schemas matched the inputs, but a max of one is allowed." %
self.__class__.__name__ cls.__name__
) )
return oneof_instances[0] return oneof_instances[0]
@@ -1465,7 +1587,7 @@ def validate_get_composed_info(constant_args, model_args, self):
composed_instances = [] composed_instances = []
allof_instances = get_allof_instances(self, model_args, constant_args) allof_instances = get_allof_instances(self, model_args, constant_args)
composed_instances.extend(allof_instances) composed_instances.extend(allof_instances)
oneof_instance = get_oneof_instance(self, model_args, constant_args) oneof_instance = get_oneof_instance(self.__class__, model_args, constant_args)
if oneof_instance is not None: if oneof_instance is not None:
composed_instances.append(oneof_instance) composed_instances.append(oneof_instance)
anyof_instances = get_anyof_instances(self, model_args, constant_args) anyof_instances = get_anyof_instances(self, model_args, constant_args)

View File

@@ -270,7 +270,7 @@ public class PythonClientExperimentalTest {
Assert.assertEquals(cm.classname, "sample.Sample"); Assert.assertEquals(cm.classname, "sample.Sample");
Assert.assertEquals(cm.classVarName, "sample"); Assert.assertEquals(cm.classVarName, "sample");
Assert.assertEquals(cm.description, "an array model"); Assert.assertEquals(cm.description, "an array model");
Assert.assertEquals(cm.vars.size(), 1); // there is one value for Childer definition Assert.assertEquals(cm.vars.size(), 0); // the array model has no vars
Assert.assertEquals(cm.parent, "list"); Assert.assertEquals(cm.parent, "list");
Assert.assertEquals(cm.imports.size(), 1); Assert.assertEquals(cm.imports.size(), 1);
Assert.assertEquals(Sets.intersection(cm.imports, Sets.newHashSet("children.Children")).size(), 1); Assert.assertEquals(Sets.intersection(cm.imports, Sets.newHashSet("children.Children")).size(), 1);
@@ -292,7 +292,7 @@ public class PythonClientExperimentalTest {
Assert.assertEquals(cm.description, "a map model"); Assert.assertEquals(cm.description, "a map model");
Assert.assertEquals(cm.vars.size(), 0); Assert.assertEquals(cm.vars.size(), 0);
Assert.assertEquals(cm.parent, null); Assert.assertEquals(cm.parent, null);
Assert.assertEquals(cm.imports.size(), 0); Assert.assertEquals(cm.imports.size(), 1);
} }
@Test(description = "parse date and date-time example value") @Test(description = "parse date and date-time example value")

View File

@@ -905,6 +905,26 @@ paths:
schema: schema:
$ref: '#/components/schemas/AnimalFarm' $ref: '#/components/schemas/AnimalFarm'
x-codegen-request-body-name: body x-codegen-request-body-name: body
/fake/refs/composed_one_of_number_with_validations:
post:
tags:
- fake
description: Test serialization of object with $refed properties
operationId: ComposedOneOfNumberWithValidations
requestBody:
description: Input model
content:
application/json:
schema:
$ref: '#/components/schemas/ComposedOneOfNumberWithValidations'
required: false
responses:
'200':
description: Output model
content:
application/json:
schema:
$ref: '#/components/schemas/ComposedOneOfNumberWithValidations'
/fake/refs/object_model_with_ref_props: /fake/refs/object_model_with_ref_props:
post: post:
tags: tags:
@@ -1837,6 +1857,14 @@ components:
type: number type: number
minimum: 10 minimum: 10
maximum: 20 maximum: 20
ComposedOneOfNumberWithValidations:
description: this is a model that allows payloads of type object or number
oneOf:
- $ref: '#/components/schemas/NumberWithValidations'
- $ref: '#/components/schemas/Animal'
- type: 'null'
- type: string
format: date
Number: Number:
type: number type: number
String: String:

View File

@@ -3,7 +3,7 @@
## Properties ## Properties
Name | Type | Description | Notes Name | Type | Description | Notes
------------ | ------------- | ------------- | ------------- ------------ | ------------- | ------------- | -------------
**value** | **str** | | defaults to '-efg' **value** | **str** | | if omitted the server will use the default value of '-efg', must be one of ["_abc", "-efg", "(xyz)", ]
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) [[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)

View File

@@ -3,7 +3,7 @@
## Properties ## Properties
Name | Type | Description | Notes Name | Type | Description | Notes
------------ | ------------- | ------------- | ------------- ------------ | ------------- | ------------- | -------------
**value** | **str** | | **value** | **str** | | must be one of ["placed", "approved", "delivered", ]
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) [[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)

View File

@@ -81,7 +81,7 @@ class AnimalFarm(ModelSimple):
and the value is attribute type. and the value is attribute type.
""" """
return { return {
'value': ([animal.Animal],), # noqa: E501 'value': ([animal.Animal],),
} }
@cached_property @cached_property
@@ -102,11 +102,11 @@ class AnimalFarm(ModelSimple):
]) ])
@convert_js_args_to_python_args @convert_js_args_to_python_args
def __init__(self, value, *args, **kwargs): # noqa: E501 def __init__(self, value, *args, **kwargs):
"""animal_farm.AnimalFarm - a model defined in OpenAPI """animal_farm.AnimalFarm - a model defined in OpenAPI
Args: Args:
value ([animal.Animal]): value ([animal.Animal]): # noqa: E501
Keyword Args: Keyword Args:
_check_type (bool): if True, values for parameters in openapi_types _check_type (bool): if True, values for parameters in openapi_types
@@ -163,13 +163,13 @@ class AnimalFarm(ModelSimple):
self._path_to_item = _path_to_item self._path_to_item = _path_to_item
self._configuration = _configuration self._configuration = _configuration
self._visited_composed_classes = _visited_composed_classes + (self.__class__,) self._visited_composed_classes = _visited_composed_classes + (self.__class__,)
self.value = value self.value = value
for var_name, var_value in six.iteritems(kwargs): if kwargs:
if var_name not in self.attribute_map and \ raise ApiTypeError(
self._configuration is not None and \ "Invalid named arguments=%s passed to %s. Remove those invalid named arguments." % (
self._configuration.discard_unknown_keys and \ kwargs,
self.additional_properties_type is None: self.__class__.__name__,
# discard variable. ),
continue path_to_item=_path_to_item,
setattr(self, var_name, var_value) valid_classes=(self.__class__,),
)

View File

@@ -81,7 +81,7 @@ class EnumClass(ModelSimple):
and the value is attribute type. and the value is attribute type.
""" """
return { return {
'value': (str,), # noqa: E501 'value': (str,),
} }
@cached_property @cached_property
@@ -102,13 +102,13 @@ class EnumClass(ModelSimple):
]) ])
@convert_js_args_to_python_args @convert_js_args_to_python_args
def __init__(self, *args, **kwargs): # noqa: E501 def __init__(self, value, *args, **kwargs):
"""enum_class.EnumClass - a model defined in OpenAPI """enum_class.EnumClass - a model defined in OpenAPI
Args: Args:
value (str): if omitted the server will use the default value of '-efg', must be one of ["_abc", "-efg", "(xyz)", ] # noqa: E501
Keyword Args: Keyword Args:
value (str): defaults to '-efg', must be one of ["_abc", "-efg", "(xyz)", ] # noqa: E501
_check_type (bool): if True, values for parameters in openapi_types _check_type (bool): if True, values for parameters in openapi_types
will be type checked and a TypeError will be will be type checked and a TypeError will be
raised if the wrong type is input. raised if the wrong type is input.
@@ -141,7 +141,6 @@ class EnumClass(ModelSimple):
_visited_composed_classes = (Animal,) _visited_composed_classes = (Animal,)
""" """
value = kwargs.get('value', '-efg')
_check_type = kwargs.pop('_check_type', True) _check_type = kwargs.pop('_check_type', True)
_spec_property_naming = kwargs.pop('_spec_property_naming', False) _spec_property_naming = kwargs.pop('_spec_property_naming', False)
_path_to_item = kwargs.pop('_path_to_item', ()) _path_to_item = kwargs.pop('_path_to_item', ())
@@ -164,13 +163,13 @@ class EnumClass(ModelSimple):
self._path_to_item = _path_to_item self._path_to_item = _path_to_item
self._configuration = _configuration self._configuration = _configuration
self._visited_composed_classes = _visited_composed_classes + (self.__class__,) self._visited_composed_classes = _visited_composed_classes + (self.__class__,)
self.value = value self.value = value
for var_name, var_value in six.iteritems(kwargs): if kwargs:
if var_name not in self.attribute_map and \ raise ApiTypeError(
self._configuration is not None and \ "Invalid named arguments=%s passed to %s. Remove those invalid named arguments." % (
self._configuration.discard_unknown_keys and \ kwargs,
self.additional_properties_type is None: self.__class__.__name__,
# discard variable. ),
continue path_to_item=_path_to_item,
setattr(self, var_name, var_value) valid_classes=(self.__class__,),
)

View File

@@ -80,7 +80,7 @@ class NumberWithValidations(ModelSimple):
and the value is attribute type. and the value is attribute type.
""" """
return { return {
'value': (float,), # noqa: E501 'value': (float,),
} }
@cached_property @cached_property
@@ -101,11 +101,11 @@ class NumberWithValidations(ModelSimple):
]) ])
@convert_js_args_to_python_args @convert_js_args_to_python_args
def __init__(self, value, *args, **kwargs): # noqa: E501 def __init__(self, value, *args, **kwargs):
"""number_with_validations.NumberWithValidations - a model defined in OpenAPI """number_with_validations.NumberWithValidations - a model defined in OpenAPI
Args: Args:
value (float): value (float): # noqa: E501
Keyword Args: Keyword Args:
_check_type (bool): if True, values for parameters in openapi_types _check_type (bool): if True, values for parameters in openapi_types
@@ -162,13 +162,13 @@ class NumberWithValidations(ModelSimple):
self._path_to_item = _path_to_item self._path_to_item = _path_to_item
self._configuration = _configuration self._configuration = _configuration
self._visited_composed_classes = _visited_composed_classes + (self.__class__,) self._visited_composed_classes = _visited_composed_classes + (self.__class__,)
self.value = value self.value = value
for var_name, var_value in six.iteritems(kwargs): if kwargs:
if var_name not in self.attribute_map and \ raise ApiTypeError(
self._configuration is not None and \ "Invalid named arguments=%s passed to %s. Remove those invalid named arguments." % (
self._configuration.discard_unknown_keys and \ kwargs,
self.additional_properties_type is None: self.__class__.__name__,
# discard variable. ),
continue path_to_item=_path_to_item,
setattr(self, var_name, var_value) valid_classes=(self.__class__,),
)

View File

@@ -81,7 +81,7 @@ class StringEnum(ModelSimple):
and the value is attribute type. and the value is attribute type.
""" """
return { return {
'value': (str,), # noqa: E501 'value': (str,),
} }
@cached_property @cached_property
@@ -102,11 +102,11 @@ class StringEnum(ModelSimple):
]) ])
@convert_js_args_to_python_args @convert_js_args_to_python_args
def __init__(self, value, *args, **kwargs): # noqa: E501 def __init__(self, value, *args, **kwargs):
"""string_enum.StringEnum - a model defined in OpenAPI """string_enum.StringEnum - a model defined in OpenAPI
Args: Args:
value (str): value (str):, must be one of ["placed", "approved", "delivered", ] # noqa: E501
Keyword Args: Keyword Args:
_check_type (bool): if True, values for parameters in openapi_types _check_type (bool): if True, values for parameters in openapi_types
@@ -163,13 +163,13 @@ class StringEnum(ModelSimple):
self._path_to_item = _path_to_item self._path_to_item = _path_to_item
self._configuration = _configuration self._configuration = _configuration
self._visited_composed_classes = _visited_composed_classes + (self.__class__,) self._visited_composed_classes = _visited_composed_classes + (self.__class__,)
self.value = value self.value = value
for var_name, var_value in six.iteritems(kwargs): if kwargs:
if var_name not in self.attribute_map and \ raise ApiTypeError(
self._configuration is not None and \ "Invalid named arguments=%s passed to %s. Remove those invalid named arguments." % (
self._configuration.discard_unknown_keys and \ kwargs,
self.additional_properties_type is None: self.__class__.__name__,
# discard variable. ),
continue path_to_item=_path_to_item,
setattr(self, var_name, var_value) valid_classes=(self.__class__,),
)

View File

@@ -62,6 +62,58 @@ class cached_property(object):
return result return result
PRIMITIVE_TYPES = (list, float, int, bool, datetime, date, str, file_type)
def allows_single_value_input(cls):
"""
This function returns True if the input composed schema model or any
descendant model allows a value only input
This is true for cases where oneOf contains items like:
oneOf:
- float
- NumberWithValidation
- StringEnum
- ArrayModel
- null
TODO: lru_cache this
"""
if (
issubclass(cls, ModelSimple) or
cls in PRIMITIVE_TYPES
):
return True
elif issubclass(cls, ModelComposed):
if not cls._composed_schemas['oneOf']:
return False
return any(allows_single_value_input(c) for c in cls._composed_schemas['oneOf'])
return False
def composed_model_input_classes(cls):
"""
This function returns a list of the possible models that can be accepted as
inputs.
TODO: lru_cache this
"""
if issubclass(cls, ModelSimple) or cls in PRIMITIVE_TYPES:
return [cls]
elif issubclass(cls, ModelNormal):
if cls.discriminator is None:
return [cls]
else:
return get_discriminated_classes(cls)
elif issubclass(cls, ModelComposed):
if not cls._composed_schemas['oneOf']:
return []
if cls.discriminator is None:
input_classes = []
for c in cls._composed_schemas['oneOf']:
input_classes.extend(composed_model_input_classes(c))
return input_classes
else:
return get_discriminated_classes(cls)
return []
class OpenApiModel(object): class OpenApiModel(object):
"""The base class for all OpenAPIModels""" """The base class for all OpenAPIModels"""
@@ -138,10 +190,18 @@ class OpenApiModel(object):
# pick a new schema/class to instantiate because a discriminator # pick a new schema/class to instantiate because a discriminator
# propertyName value was passed in # propertyName value was passed in
if len(args) == 1 and args[0] is None and is_type_nullable(cls): if len(args) == 1:
arg = args[0]
if arg is None and is_type_nullable(cls):
# The input data is the 'null' value and the type is nullable. # The input data is the 'null' value and the type is nullable.
return None return None
if issubclass(cls, ModelComposed) and allows_single_value_input(cls):
model_kwargs = {}
oneof_instance = get_oneof_instance(cls, model_kwargs, kwargs, model_arg=arg)
return oneof_instance
visited_composed_classes = kwargs.get('_visited_composed_classes', ()) visited_composed_classes = kwargs.get('_visited_composed_classes', ())
if ( if (
cls.discriminator is None or cls.discriminator is None or
@@ -508,6 +568,10 @@ UPCONVERSION_TYPE_PAIRS = (
(int, float), # A float may be serialized as an integer, e.g. '3' is a valid serialized float. (int, float), # A float may be serialized as an integer, e.g. '3' is a valid serialized float.
(list, ModelComposed), (list, ModelComposed),
(dict, ModelComposed), (dict, ModelComposed),
(str, ModelComposed),
(int, ModelComposed),
(float, ModelComposed),
(list, ModelComposed),
(list, ModelNormal), (list, ModelNormal),
(dict, ModelNormal), (dict, ModelNormal),
(str, ModelSimple), (str, ModelSimple),
@@ -886,20 +950,53 @@ def remove_uncoercible(required_types_classes, current_item, spec_property_namin
results_classes.append(required_type_class) results_classes.append(required_type_class)
return results_classes return results_classes
def get_discriminated_classes(cls):
"""
Returns all the classes that a discriminator converts to
TODO: lru_cache this
"""
possible_classes = []
key = list(cls.discriminator.keys())[0]
if is_type_nullable(cls):
possible_classes.append(cls)
for discr_cls in cls.discriminator[key].values():
if hasattr(discr_cls, 'discriminator') and discr_cls.discriminator is not None:
possible_classes.extend(get_discriminated_classes(discr_cls))
else:
possible_classes.append(discr_cls)
return possible_classes
def get_required_type_classes(required_types_mixed):
def get_possible_classes(cls, from_server_context):
# TODO: lru_cache this
possible_classes = [cls]
if from_server_context:
return possible_classes
if hasattr(cls, 'discriminator') and cls.discriminator is not None:
possible_classes = []
possible_classes.extend(get_discriminated_classes(cls))
elif issubclass(cls, ModelComposed):
possible_classes.extend(composed_model_input_classes(cls))
return possible_classes
def get_required_type_classes(required_types_mixed, spec_property_naming):
"""Converts the tuple required_types into a tuple and a dict described """Converts the tuple required_types into a tuple and a dict described
below below
Args: Args:
required_types_mixed (tuple/list): will contain either classes or required_types_mixed (tuple/list): will contain either classes or
instance of list or dict instance of list or dict
spec_property_naming (bool): if True these values came from the
server, and we use the data types in our endpoints.
If False, we are client side and we need to include
oneOf and discriminator classes inside the data types in our endpoints
Returns: Returns:
(valid_classes, dict_valid_class_to_child_types_mixed): (valid_classes, dict_valid_class_to_child_types_mixed):
valid_classes (tuple): the valid classes that the current item valid_classes (tuple): the valid classes that the current item
should be should be
dict_valid_class_to_child_types_mixed (doct): dict_valid_class_to_child_types_mixed (dict):
valid_class (class): this is the key valid_class (class): this is the key
child_types_mixed (list/dict/tuple): describes the valid child child_types_mixed (list/dict/tuple): describes the valid child
types types
@@ -917,7 +1014,7 @@ def get_required_type_classes(required_types_mixed):
valid_classes.append(dict) valid_classes.append(dict)
child_req_types_by_current_type[dict] = required_type[str] child_req_types_by_current_type[dict] = required_type[str]
else: else:
valid_classes.append(required_type) valid_classes.extend(get_possible_classes(required_type, spec_property_naming))
return tuple(valid_classes), child_req_types_by_current_type return tuple(valid_classes), child_req_types_by_current_type
@@ -1070,7 +1167,7 @@ def deserialize_model(model_data, model_class, path_to_item, check_type,
"""Deserializes model_data to model instance. """Deserializes model_data to model instance.
Args: Args:
model_data (list/dict): data to instantiate the model model_data (int/str/float/bool/none_type/list/dict): data to instantiate the model
model_class (OpenApiModel): the model class model_class (OpenApiModel): the model class
path_to_item (list): path to the model in the received data path_to_item (list): path to the model in the received data
check_type (bool): whether to check the data tupe for the values in check_type (bool): whether to check the data tupe for the values in
@@ -1096,14 +1193,14 @@ def deserialize_model(model_data, model_class, path_to_item, check_type,
_spec_property_naming=spec_property_naming) _spec_property_naming=spec_property_naming)
if issubclass(model_class, ModelSimple): if issubclass(model_class, ModelSimple):
instance = model_class(value=model_data, **kw_args) return model_class(model_data, **kw_args)
return instance elif isinstance(model_data, list):
if isinstance(model_data, list): return model_class(*model_data, **kw_args)
instance = model_class(*model_data, **kw_args)
if isinstance(model_data, dict): if isinstance(model_data, dict):
kw_args.update(model_data) kw_args.update(model_data)
instance = model_class(**kw_args) return model_class(**kw_args)
return instance elif isinstance(model_data, PRIMITIVE_TYPES):
return model_class(model_data, **kw_args)
def deserialize_file(response_data, configuration, content_disposition=None): def deserialize_file(response_data, configuration, content_disposition=None):
@@ -1286,7 +1383,7 @@ def validate_and_convert_types(input_value, required_types_mixed, path_to_item,
Raises: Raises:
ApiTypeError ApiTypeError
""" """
results = get_required_type_classes(required_types_mixed) results = get_required_type_classes(required_types_mixed, spec_property_naming)
valid_classes, child_req_types_by_current_type = results valid_classes, child_req_types_by_current_type = results
input_class_simple = get_simple_class(input_value) input_class_simple = get_simple_class(input_value)
@@ -1523,7 +1620,7 @@ def get_allof_instances(self, model_args, constant_args):
return composed_instances return composed_instances
def get_oneof_instance(self, model_args, constant_args): def get_oneof_instance(cls, model_kwargs, constant_kwargs, model_arg=None):
""" """
Find the oneOf schema that matches the input data (e.g. payload). Find the oneOf schema that matches the input data (e.g. payload).
If exactly one schema matches the input data, an instance of that schema If exactly one schema matches the input data, an instance of that schema
@@ -1531,25 +1628,33 @@ def get_oneof_instance(self, model_args, constant_args):
If zero or more than one schema match the input data, an exception is raised. If zero or more than one schema match the input data, an exception is raised.
In OAS 3.x, the payload MUST, by validation, match exactly one of the In OAS 3.x, the payload MUST, by validation, match exactly one of the
schemas described by oneOf. schemas described by oneOf.
Args: Args:
self: the class we are handling cls: the class we are handling
model_args (dict): var_name to var_value model_kwargs (dict): var_name to var_value
The input data, e.g. the payload that must match a oneOf schema The input data, e.g. the payload that must match a oneOf schema
in the OpenAPI document. in the OpenAPI document.
constant_args (dict): var_name to var_value constant_kwargs (dict): var_name to var_value
args that every model requires, including configuration, server args that every model requires, including configuration, server
and path to item. and path to item.
Kwargs:
model_arg: (int, float, bool, str, date, datetime, ModelSimple, None):
the value to assign to a primitive class or ModelSimple class
Notes:
- this is only passed in when oneOf includes types which are not object
- None is used to suppress handling of model_arg, nullable models are handled in __new__
Returns Returns
oneof_instance (instance) oneof_instance (instance)
""" """
if len(self._composed_schemas['oneOf']) == 0: if len(cls._composed_schemas['oneOf']) == 0:
return None return None
oneof_instances = [] oneof_instances = []
# Iterate over each oneOf schema and determine if the input data # Iterate over each oneOf schema and determine if the input data
# matches the oneOf schemas. # matches the oneOf schemas.
for oneof_class in self._composed_schemas['oneOf']: for oneof_class in cls._composed_schemas['oneOf']:
# The composed oneOf schema allows the 'null' type and the input data # The composed oneOf schema allows the 'null' type and the input data
# is the null value. This is a OAS >= 3.1 feature. # is the null value. This is a OAS >= 3.1 feature.
if oneof_class is none_type: if oneof_class is none_type:
@@ -1557,9 +1662,12 @@ def get_oneof_instance(self, model_args, constant_args):
# none_type deserialization is handled in the __new__ method # none_type deserialization is handled in the __new__ method
continue continue
single_value_input = allows_single_value_input(oneof_class)
if not single_value_input:
# transform js keys from input data to python keys in fixed_model_args # transform js keys from input data to python keys in fixed_model_args
fixed_model_args = change_keys_js_to_python( fixed_model_args = change_keys_js_to_python(
model_args, oneof_class) model_kwargs, oneof_class)
# Extract a dict with the properties that are declared in the oneOf schema. # Extract a dict with the properties that are declared in the oneOf schema.
# Undeclared properties (e.g. properties that are allowed because of the # Undeclared properties (e.g. properties that are allowed because of the
@@ -1576,9 +1684,23 @@ def get_oneof_instance(self, model_args, constant_args):
continue continue
# and use it to make the instance # and use it to make the instance
kwargs.update(constant_args) kwargs.update(constant_kwargs)
try: try:
if not single_value_input:
oneof_instance = oneof_class(**kwargs) oneof_instance = oneof_class(**kwargs)
else:
if issubclass(oneof_class, ModelSimple):
oneof_instance = oneof_class(model_arg, **constant_kwargs)
elif oneof_class in PRIMITIVE_TYPES:
oneof_instance = validate_and_convert_types(
model_arg,
(oneof_class,),
constant_kwargs['_path_to_item'],
constant_kwargs['_spec_property_naming'],
constant_kwargs['_check_type'],
configuration=constant_kwargs['_configuration']
)
oneof_instances.append(oneof_instance) oneof_instances.append(oneof_instance)
except Exception: except Exception:
pass pass
@@ -1586,13 +1708,13 @@ def get_oneof_instance(self, model_args, constant_args):
raise ApiValueError( raise ApiValueError(
"Invalid inputs given to generate an instance of %s. None " "Invalid inputs given to generate an instance of %s. None "
"of the oneOf schemas matched the input data." % "of the oneOf schemas matched the input data." %
self.__class__.__name__ cls.__name__
) )
elif len(oneof_instances) > 1: elif len(oneof_instances) > 1:
raise ApiValueError( raise ApiValueError(
"Invalid inputs given to generate an instance of %s. Multiple " "Invalid inputs given to generate an instance of %s. Multiple "
"oneOf schemas matched the inputs, but a max of one is allowed." % "oneOf schemas matched the inputs, but a max of one is allowed." %
self.__class__.__name__ cls.__name__
) )
return oneof_instances[0] return oneof_instances[0]
@@ -1732,7 +1854,7 @@ def validate_get_composed_info(constant_args, model_args, self):
composed_instances = [] composed_instances = []
allof_instances = get_allof_instances(self, model_args, constant_args) allof_instances = get_allof_instances(self, model_args, constant_args)
composed_instances.extend(allof_instances) composed_instances.extend(allof_instances)
oneof_instance = get_oneof_instance(self, model_args, constant_args) oneof_instance = get_oneof_instance(self.__class__, model_args, constant_args)
if oneof_instance is not None: if oneof_instance is not None:
composed_instances.append(oneof_instance) composed_instances.append(oneof_instance)
anyof_instances = get_anyof_instances(self, model_args, constant_args) anyof_instances = get_anyof_instances(self, model_args, constant_args)

View File

@@ -62,6 +62,58 @@ class cached_property(object):
return result return result
PRIMITIVE_TYPES = (list, float, int, bool, datetime, date, str, file_type)
def allows_single_value_input(cls):
"""
This function returns True if the input composed schema model or any
descendant model allows a value only input
This is true for cases where oneOf contains items like:
oneOf:
- float
- NumberWithValidation
- StringEnum
- ArrayModel
- null
TODO: lru_cache this
"""
if (
issubclass(cls, ModelSimple) or
cls in PRIMITIVE_TYPES
):
return True
elif issubclass(cls, ModelComposed):
if not cls._composed_schemas['oneOf']:
return False
return any(allows_single_value_input(c) for c in cls._composed_schemas['oneOf'])
return False
def composed_model_input_classes(cls):
"""
This function returns a list of the possible models that can be accepted as
inputs.
TODO: lru_cache this
"""
if issubclass(cls, ModelSimple) or cls in PRIMITIVE_TYPES:
return [cls]
elif issubclass(cls, ModelNormal):
if cls.discriminator is None:
return [cls]
else:
return get_discriminated_classes(cls)
elif issubclass(cls, ModelComposed):
if not cls._composed_schemas['oneOf']:
return []
if cls.discriminator is None:
input_classes = []
for c in cls._composed_schemas['oneOf']:
input_classes.extend(composed_model_input_classes(c))
return input_classes
else:
return get_discriminated_classes(cls)
return []
class OpenApiModel(object): class OpenApiModel(object):
"""The base class for all OpenAPIModels""" """The base class for all OpenAPIModels"""
@@ -138,10 +190,18 @@ class OpenApiModel(object):
# pick a new schema/class to instantiate because a discriminator # pick a new schema/class to instantiate because a discriminator
# propertyName value was passed in # propertyName value was passed in
if len(args) == 1 and args[0] is None and is_type_nullable(cls): if len(args) == 1:
arg = args[0]
if arg is None and is_type_nullable(cls):
# The input data is the 'null' value and the type is nullable. # The input data is the 'null' value and the type is nullable.
return None return None
if issubclass(cls, ModelComposed) and allows_single_value_input(cls):
model_kwargs = {}
oneof_instance = get_oneof_instance(cls, model_kwargs, kwargs, model_arg=arg)
return oneof_instance
visited_composed_classes = kwargs.get('_visited_composed_classes', ()) visited_composed_classes = kwargs.get('_visited_composed_classes', ())
if ( if (
cls.discriminator is None or cls.discriminator is None or
@@ -508,6 +568,10 @@ UPCONVERSION_TYPE_PAIRS = (
(int, float), # A float may be serialized as an integer, e.g. '3' is a valid serialized float. (int, float), # A float may be serialized as an integer, e.g. '3' is a valid serialized float.
(list, ModelComposed), (list, ModelComposed),
(dict, ModelComposed), (dict, ModelComposed),
(str, ModelComposed),
(int, ModelComposed),
(float, ModelComposed),
(list, ModelComposed),
(list, ModelNormal), (list, ModelNormal),
(dict, ModelNormal), (dict, ModelNormal),
(str, ModelSimple), (str, ModelSimple),
@@ -886,20 +950,53 @@ def remove_uncoercible(required_types_classes, current_item, spec_property_namin
results_classes.append(required_type_class) results_classes.append(required_type_class)
return results_classes return results_classes
def get_discriminated_classes(cls):
"""
Returns all the classes that a discriminator converts to
TODO: lru_cache this
"""
possible_classes = []
key = list(cls.discriminator.keys())[0]
if is_type_nullable(cls):
possible_classes.append(cls)
for discr_cls in cls.discriminator[key].values():
if hasattr(discr_cls, 'discriminator') and discr_cls.discriminator is not None:
possible_classes.extend(get_discriminated_classes(discr_cls))
else:
possible_classes.append(discr_cls)
return possible_classes
def get_required_type_classes(required_types_mixed):
def get_possible_classes(cls, from_server_context):
# TODO: lru_cache this
possible_classes = [cls]
if from_server_context:
return possible_classes
if hasattr(cls, 'discriminator') and cls.discriminator is not None:
possible_classes = []
possible_classes.extend(get_discriminated_classes(cls))
elif issubclass(cls, ModelComposed):
possible_classes.extend(composed_model_input_classes(cls))
return possible_classes
def get_required_type_classes(required_types_mixed, spec_property_naming):
"""Converts the tuple required_types into a tuple and a dict described """Converts the tuple required_types into a tuple and a dict described
below below
Args: Args:
required_types_mixed (tuple/list): will contain either classes or required_types_mixed (tuple/list): will contain either classes or
instance of list or dict instance of list or dict
spec_property_naming (bool): if True these values came from the
server, and we use the data types in our endpoints.
If False, we are client side and we need to include
oneOf and discriminator classes inside the data types in our endpoints
Returns: Returns:
(valid_classes, dict_valid_class_to_child_types_mixed): (valid_classes, dict_valid_class_to_child_types_mixed):
valid_classes (tuple): the valid classes that the current item valid_classes (tuple): the valid classes that the current item
should be should be
dict_valid_class_to_child_types_mixed (doct): dict_valid_class_to_child_types_mixed (dict):
valid_class (class): this is the key valid_class (class): this is the key
child_types_mixed (list/dict/tuple): describes the valid child child_types_mixed (list/dict/tuple): describes the valid child
types types
@@ -917,7 +1014,7 @@ def get_required_type_classes(required_types_mixed):
valid_classes.append(dict) valid_classes.append(dict)
child_req_types_by_current_type[dict] = required_type[str] child_req_types_by_current_type[dict] = required_type[str]
else: else:
valid_classes.append(required_type) valid_classes.extend(get_possible_classes(required_type, spec_property_naming))
return tuple(valid_classes), child_req_types_by_current_type return tuple(valid_classes), child_req_types_by_current_type
@@ -1070,7 +1167,7 @@ def deserialize_model(model_data, model_class, path_to_item, check_type,
"""Deserializes model_data to model instance. """Deserializes model_data to model instance.
Args: Args:
model_data (list/dict): data to instantiate the model model_data (int/str/float/bool/none_type/list/dict): data to instantiate the model
model_class (OpenApiModel): the model class model_class (OpenApiModel): the model class
path_to_item (list): path to the model in the received data path_to_item (list): path to the model in the received data
check_type (bool): whether to check the data tupe for the values in check_type (bool): whether to check the data tupe for the values in
@@ -1096,14 +1193,14 @@ def deserialize_model(model_data, model_class, path_to_item, check_type,
_spec_property_naming=spec_property_naming) _spec_property_naming=spec_property_naming)
if issubclass(model_class, ModelSimple): if issubclass(model_class, ModelSimple):
instance = model_class(value=model_data, **kw_args) return model_class(model_data, **kw_args)
return instance elif isinstance(model_data, list):
if isinstance(model_data, list): return model_class(*model_data, **kw_args)
instance = model_class(*model_data, **kw_args)
if isinstance(model_data, dict): if isinstance(model_data, dict):
kw_args.update(model_data) kw_args.update(model_data)
instance = model_class(**kw_args) return model_class(**kw_args)
return instance elif isinstance(model_data, PRIMITIVE_TYPES):
return model_class(model_data, **kw_args)
def deserialize_file(response_data, configuration, content_disposition=None): def deserialize_file(response_data, configuration, content_disposition=None):
@@ -1286,7 +1383,7 @@ def validate_and_convert_types(input_value, required_types_mixed, path_to_item,
Raises: Raises:
ApiTypeError ApiTypeError
""" """
results = get_required_type_classes(required_types_mixed) results = get_required_type_classes(required_types_mixed, spec_property_naming)
valid_classes, child_req_types_by_current_type = results valid_classes, child_req_types_by_current_type = results
input_class_simple = get_simple_class(input_value) input_class_simple = get_simple_class(input_value)
@@ -1523,7 +1620,7 @@ def get_allof_instances(self, model_args, constant_args):
return composed_instances return composed_instances
def get_oneof_instance(self, model_args, constant_args): def get_oneof_instance(cls, model_kwargs, constant_kwargs, model_arg=None):
""" """
Find the oneOf schema that matches the input data (e.g. payload). Find the oneOf schema that matches the input data (e.g. payload).
If exactly one schema matches the input data, an instance of that schema If exactly one schema matches the input data, an instance of that schema
@@ -1531,25 +1628,33 @@ def get_oneof_instance(self, model_args, constant_args):
If zero or more than one schema match the input data, an exception is raised. If zero or more than one schema match the input data, an exception is raised.
In OAS 3.x, the payload MUST, by validation, match exactly one of the In OAS 3.x, the payload MUST, by validation, match exactly one of the
schemas described by oneOf. schemas described by oneOf.
Args: Args:
self: the class we are handling cls: the class we are handling
model_args (dict): var_name to var_value model_kwargs (dict): var_name to var_value
The input data, e.g. the payload that must match a oneOf schema The input data, e.g. the payload that must match a oneOf schema
in the OpenAPI document. in the OpenAPI document.
constant_args (dict): var_name to var_value constant_kwargs (dict): var_name to var_value
args that every model requires, including configuration, server args that every model requires, including configuration, server
and path to item. and path to item.
Kwargs:
model_arg: (int, float, bool, str, date, datetime, ModelSimple, None):
the value to assign to a primitive class or ModelSimple class
Notes:
- this is only passed in when oneOf includes types which are not object
- None is used to suppress handling of model_arg, nullable models are handled in __new__
Returns Returns
oneof_instance (instance) oneof_instance (instance)
""" """
if len(self._composed_schemas['oneOf']) == 0: if len(cls._composed_schemas['oneOf']) == 0:
return None return None
oneof_instances = [] oneof_instances = []
# Iterate over each oneOf schema and determine if the input data # Iterate over each oneOf schema and determine if the input data
# matches the oneOf schemas. # matches the oneOf schemas.
for oneof_class in self._composed_schemas['oneOf']: for oneof_class in cls._composed_schemas['oneOf']:
# The composed oneOf schema allows the 'null' type and the input data # The composed oneOf schema allows the 'null' type and the input data
# is the null value. This is a OAS >= 3.1 feature. # is the null value. This is a OAS >= 3.1 feature.
if oneof_class is none_type: if oneof_class is none_type:
@@ -1557,9 +1662,12 @@ def get_oneof_instance(self, model_args, constant_args):
# none_type deserialization is handled in the __new__ method # none_type deserialization is handled in the __new__ method
continue continue
single_value_input = allows_single_value_input(oneof_class)
if not single_value_input:
# transform js keys from input data to python keys in fixed_model_args # transform js keys from input data to python keys in fixed_model_args
fixed_model_args = change_keys_js_to_python( fixed_model_args = change_keys_js_to_python(
model_args, oneof_class) model_kwargs, oneof_class)
# Extract a dict with the properties that are declared in the oneOf schema. # Extract a dict with the properties that are declared in the oneOf schema.
# Undeclared properties (e.g. properties that are allowed because of the # Undeclared properties (e.g. properties that are allowed because of the
@@ -1576,9 +1684,23 @@ def get_oneof_instance(self, model_args, constant_args):
continue continue
# and use it to make the instance # and use it to make the instance
kwargs.update(constant_args) kwargs.update(constant_kwargs)
try: try:
if not single_value_input:
oneof_instance = oneof_class(**kwargs) oneof_instance = oneof_class(**kwargs)
else:
if issubclass(oneof_class, ModelSimple):
oneof_instance = oneof_class(model_arg, **constant_kwargs)
elif oneof_class in PRIMITIVE_TYPES:
oneof_instance = validate_and_convert_types(
model_arg,
(oneof_class,),
constant_kwargs['_path_to_item'],
constant_kwargs['_spec_property_naming'],
constant_kwargs['_check_type'],
configuration=constant_kwargs['_configuration']
)
oneof_instances.append(oneof_instance) oneof_instances.append(oneof_instance)
except Exception: except Exception:
pass pass
@@ -1586,13 +1708,13 @@ def get_oneof_instance(self, model_args, constant_args):
raise ApiValueError( raise ApiValueError(
"Invalid inputs given to generate an instance of %s. None " "Invalid inputs given to generate an instance of %s. None "
"of the oneOf schemas matched the input data." % "of the oneOf schemas matched the input data." %
self.__class__.__name__ cls.__name__
) )
elif len(oneof_instances) > 1: elif len(oneof_instances) > 1:
raise ApiValueError( raise ApiValueError(
"Invalid inputs given to generate an instance of %s. Multiple " "Invalid inputs given to generate an instance of %s. Multiple "
"oneOf schemas matched the inputs, but a max of one is allowed." % "oneOf schemas matched the inputs, but a max of one is allowed." %
self.__class__.__name__ cls.__name__
) )
return oneof_instances[0] return oneof_instances[0]
@@ -1732,7 +1854,7 @@ def validate_get_composed_info(constant_args, model_args, self):
composed_instances = [] composed_instances = []
allof_instances = get_allof_instances(self, model_args, constant_args) allof_instances = get_allof_instances(self, model_args, constant_args)
composed_instances.extend(allof_instances) composed_instances.extend(allof_instances)
oneof_instance = get_oneof_instance(self, model_args, constant_args) oneof_instance = get_oneof_instance(self.__class__, model_args, constant_args)
if oneof_instance is not None: if oneof_instance is not None:
composed_instances.append(oneof_instance) composed_instances.append(oneof_instance)
anyof_instances = get_anyof_instances(self, model_args, constant_args) anyof_instances = get_anyof_instances(self, model_args, constant_args)

View File

@@ -62,6 +62,58 @@ class cached_property(object):
return result return result
PRIMITIVE_TYPES = (list, float, int, bool, datetime, date, str, file_type)
def allows_single_value_input(cls):
"""
This function returns True if the input composed schema model or any
descendant model allows a value only input
This is true for cases where oneOf contains items like:
oneOf:
- float
- NumberWithValidation
- StringEnum
- ArrayModel
- null
TODO: lru_cache this
"""
if (
issubclass(cls, ModelSimple) or
cls in PRIMITIVE_TYPES
):
return True
elif issubclass(cls, ModelComposed):
if not cls._composed_schemas['oneOf']:
return False
return any(allows_single_value_input(c) for c in cls._composed_schemas['oneOf'])
return False
def composed_model_input_classes(cls):
"""
This function returns a list of the possible models that can be accepted as
inputs.
TODO: lru_cache this
"""
if issubclass(cls, ModelSimple) or cls in PRIMITIVE_TYPES:
return [cls]
elif issubclass(cls, ModelNormal):
if cls.discriminator is None:
return [cls]
else:
return get_discriminated_classes(cls)
elif issubclass(cls, ModelComposed):
if not cls._composed_schemas['oneOf']:
return []
if cls.discriminator is None:
input_classes = []
for c in cls._composed_schemas['oneOf']:
input_classes.extend(composed_model_input_classes(c))
return input_classes
else:
return get_discriminated_classes(cls)
return []
class OpenApiModel(object): class OpenApiModel(object):
"""The base class for all OpenAPIModels""" """The base class for all OpenAPIModels"""
@@ -138,10 +190,18 @@ class OpenApiModel(object):
# pick a new schema/class to instantiate because a discriminator # pick a new schema/class to instantiate because a discriminator
# propertyName value was passed in # propertyName value was passed in
if len(args) == 1 and args[0] is None and is_type_nullable(cls): if len(args) == 1:
arg = args[0]
if arg is None and is_type_nullable(cls):
# The input data is the 'null' value and the type is nullable. # The input data is the 'null' value and the type is nullable.
return None return None
if issubclass(cls, ModelComposed) and allows_single_value_input(cls):
model_kwargs = {}
oneof_instance = get_oneof_instance(cls, model_kwargs, kwargs, model_arg=arg)
return oneof_instance
visited_composed_classes = kwargs.get('_visited_composed_classes', ()) visited_composed_classes = kwargs.get('_visited_composed_classes', ())
if ( if (
cls.discriminator is None or cls.discriminator is None or
@@ -508,6 +568,10 @@ UPCONVERSION_TYPE_PAIRS = (
(int, float), # A float may be serialized as an integer, e.g. '3' is a valid serialized float. (int, float), # A float may be serialized as an integer, e.g. '3' is a valid serialized float.
(list, ModelComposed), (list, ModelComposed),
(dict, ModelComposed), (dict, ModelComposed),
(str, ModelComposed),
(int, ModelComposed),
(float, ModelComposed),
(list, ModelComposed),
(list, ModelNormal), (list, ModelNormal),
(dict, ModelNormal), (dict, ModelNormal),
(str, ModelSimple), (str, ModelSimple),
@@ -886,20 +950,53 @@ def remove_uncoercible(required_types_classes, current_item, spec_property_namin
results_classes.append(required_type_class) results_classes.append(required_type_class)
return results_classes return results_classes
def get_discriminated_classes(cls):
"""
Returns all the classes that a discriminator converts to
TODO: lru_cache this
"""
possible_classes = []
key = list(cls.discriminator.keys())[0]
if is_type_nullable(cls):
possible_classes.append(cls)
for discr_cls in cls.discriminator[key].values():
if hasattr(discr_cls, 'discriminator') and discr_cls.discriminator is not None:
possible_classes.extend(get_discriminated_classes(discr_cls))
else:
possible_classes.append(discr_cls)
return possible_classes
def get_required_type_classes(required_types_mixed):
def get_possible_classes(cls, from_server_context):
# TODO: lru_cache this
possible_classes = [cls]
if from_server_context:
return possible_classes
if hasattr(cls, 'discriminator') and cls.discriminator is not None:
possible_classes = []
possible_classes.extend(get_discriminated_classes(cls))
elif issubclass(cls, ModelComposed):
possible_classes.extend(composed_model_input_classes(cls))
return possible_classes
def get_required_type_classes(required_types_mixed, spec_property_naming):
"""Converts the tuple required_types into a tuple and a dict described """Converts the tuple required_types into a tuple and a dict described
below below
Args: Args:
required_types_mixed (tuple/list): will contain either classes or required_types_mixed (tuple/list): will contain either classes or
instance of list or dict instance of list or dict
spec_property_naming (bool): if True these values came from the
server, and we use the data types in our endpoints.
If False, we are client side and we need to include
oneOf and discriminator classes inside the data types in our endpoints
Returns: Returns:
(valid_classes, dict_valid_class_to_child_types_mixed): (valid_classes, dict_valid_class_to_child_types_mixed):
valid_classes (tuple): the valid classes that the current item valid_classes (tuple): the valid classes that the current item
should be should be
dict_valid_class_to_child_types_mixed (doct): dict_valid_class_to_child_types_mixed (dict):
valid_class (class): this is the key valid_class (class): this is the key
child_types_mixed (list/dict/tuple): describes the valid child child_types_mixed (list/dict/tuple): describes the valid child
types types
@@ -917,7 +1014,7 @@ def get_required_type_classes(required_types_mixed):
valid_classes.append(dict) valid_classes.append(dict)
child_req_types_by_current_type[dict] = required_type[str] child_req_types_by_current_type[dict] = required_type[str]
else: else:
valid_classes.append(required_type) valid_classes.extend(get_possible_classes(required_type, spec_property_naming))
return tuple(valid_classes), child_req_types_by_current_type return tuple(valid_classes), child_req_types_by_current_type
@@ -1070,7 +1167,7 @@ def deserialize_model(model_data, model_class, path_to_item, check_type,
"""Deserializes model_data to model instance. """Deserializes model_data to model instance.
Args: Args:
model_data (list/dict): data to instantiate the model model_data (int/str/float/bool/none_type/list/dict): data to instantiate the model
model_class (OpenApiModel): the model class model_class (OpenApiModel): the model class
path_to_item (list): path to the model in the received data path_to_item (list): path to the model in the received data
check_type (bool): whether to check the data tupe for the values in check_type (bool): whether to check the data tupe for the values in
@@ -1096,14 +1193,14 @@ def deserialize_model(model_data, model_class, path_to_item, check_type,
_spec_property_naming=spec_property_naming) _spec_property_naming=spec_property_naming)
if issubclass(model_class, ModelSimple): if issubclass(model_class, ModelSimple):
instance = model_class(value=model_data, **kw_args) return model_class(model_data, **kw_args)
return instance elif isinstance(model_data, list):
if isinstance(model_data, list): return model_class(*model_data, **kw_args)
instance = model_class(*model_data, **kw_args)
if isinstance(model_data, dict): if isinstance(model_data, dict):
kw_args.update(model_data) kw_args.update(model_data)
instance = model_class(**kw_args) return model_class(**kw_args)
return instance elif isinstance(model_data, PRIMITIVE_TYPES):
return model_class(model_data, **kw_args)
def deserialize_file(response_data, configuration, content_disposition=None): def deserialize_file(response_data, configuration, content_disposition=None):
@@ -1286,7 +1383,7 @@ def validate_and_convert_types(input_value, required_types_mixed, path_to_item,
Raises: Raises:
ApiTypeError ApiTypeError
""" """
results = get_required_type_classes(required_types_mixed) results = get_required_type_classes(required_types_mixed, spec_property_naming)
valid_classes, child_req_types_by_current_type = results valid_classes, child_req_types_by_current_type = results
input_class_simple = get_simple_class(input_value) input_class_simple = get_simple_class(input_value)
@@ -1523,7 +1620,7 @@ def get_allof_instances(self, model_args, constant_args):
return composed_instances return composed_instances
def get_oneof_instance(self, model_args, constant_args): def get_oneof_instance(cls, model_kwargs, constant_kwargs, model_arg=None):
""" """
Find the oneOf schema that matches the input data (e.g. payload). Find the oneOf schema that matches the input data (e.g. payload).
If exactly one schema matches the input data, an instance of that schema If exactly one schema matches the input data, an instance of that schema
@@ -1531,25 +1628,33 @@ def get_oneof_instance(self, model_args, constant_args):
If zero or more than one schema match the input data, an exception is raised. If zero or more than one schema match the input data, an exception is raised.
In OAS 3.x, the payload MUST, by validation, match exactly one of the In OAS 3.x, the payload MUST, by validation, match exactly one of the
schemas described by oneOf. schemas described by oneOf.
Args: Args:
self: the class we are handling cls: the class we are handling
model_args (dict): var_name to var_value model_kwargs (dict): var_name to var_value
The input data, e.g. the payload that must match a oneOf schema The input data, e.g. the payload that must match a oneOf schema
in the OpenAPI document. in the OpenAPI document.
constant_args (dict): var_name to var_value constant_kwargs (dict): var_name to var_value
args that every model requires, including configuration, server args that every model requires, including configuration, server
and path to item. and path to item.
Kwargs:
model_arg: (int, float, bool, str, date, datetime, ModelSimple, None):
the value to assign to a primitive class or ModelSimple class
Notes:
- this is only passed in when oneOf includes types which are not object
- None is used to suppress handling of model_arg, nullable models are handled in __new__
Returns Returns
oneof_instance (instance) oneof_instance (instance)
""" """
if len(self._composed_schemas['oneOf']) == 0: if len(cls._composed_schemas['oneOf']) == 0:
return None return None
oneof_instances = [] oneof_instances = []
# Iterate over each oneOf schema and determine if the input data # Iterate over each oneOf schema and determine if the input data
# matches the oneOf schemas. # matches the oneOf schemas.
for oneof_class in self._composed_schemas['oneOf']: for oneof_class in cls._composed_schemas['oneOf']:
# The composed oneOf schema allows the 'null' type and the input data # The composed oneOf schema allows the 'null' type and the input data
# is the null value. This is a OAS >= 3.1 feature. # is the null value. This is a OAS >= 3.1 feature.
if oneof_class is none_type: if oneof_class is none_type:
@@ -1557,9 +1662,12 @@ def get_oneof_instance(self, model_args, constant_args):
# none_type deserialization is handled in the __new__ method # none_type deserialization is handled in the __new__ method
continue continue
single_value_input = allows_single_value_input(oneof_class)
if not single_value_input:
# transform js keys from input data to python keys in fixed_model_args # transform js keys from input data to python keys in fixed_model_args
fixed_model_args = change_keys_js_to_python( fixed_model_args = change_keys_js_to_python(
model_args, oneof_class) model_kwargs, oneof_class)
# Extract a dict with the properties that are declared in the oneOf schema. # Extract a dict with the properties that are declared in the oneOf schema.
# Undeclared properties (e.g. properties that are allowed because of the # Undeclared properties (e.g. properties that are allowed because of the
@@ -1576,9 +1684,23 @@ def get_oneof_instance(self, model_args, constant_args):
continue continue
# and use it to make the instance # and use it to make the instance
kwargs.update(constant_args) kwargs.update(constant_kwargs)
try: try:
if not single_value_input:
oneof_instance = oneof_class(**kwargs) oneof_instance = oneof_class(**kwargs)
else:
if issubclass(oneof_class, ModelSimple):
oneof_instance = oneof_class(model_arg, **constant_kwargs)
elif oneof_class in PRIMITIVE_TYPES:
oneof_instance = validate_and_convert_types(
model_arg,
(oneof_class,),
constant_kwargs['_path_to_item'],
constant_kwargs['_spec_property_naming'],
constant_kwargs['_check_type'],
configuration=constant_kwargs['_configuration']
)
oneof_instances.append(oneof_instance) oneof_instances.append(oneof_instance)
except Exception: except Exception:
pass pass
@@ -1586,13 +1708,13 @@ def get_oneof_instance(self, model_args, constant_args):
raise ApiValueError( raise ApiValueError(
"Invalid inputs given to generate an instance of %s. None " "Invalid inputs given to generate an instance of %s. None "
"of the oneOf schemas matched the input data." % "of the oneOf schemas matched the input data." %
self.__class__.__name__ cls.__name__
) )
elif len(oneof_instances) > 1: elif len(oneof_instances) > 1:
raise ApiValueError( raise ApiValueError(
"Invalid inputs given to generate an instance of %s. Multiple " "Invalid inputs given to generate an instance of %s. Multiple "
"oneOf schemas matched the inputs, but a max of one is allowed." % "oneOf schemas matched the inputs, but a max of one is allowed." %
self.__class__.__name__ cls.__name__
) )
return oneof_instances[0] return oneof_instances[0]
@@ -1732,7 +1854,7 @@ def validate_get_composed_info(constant_args, model_args, self):
composed_instances = [] composed_instances = []
allof_instances = get_allof_instances(self, model_args, constant_args) allof_instances = get_allof_instances(self, model_args, constant_args)
composed_instances.extend(allof_instances) composed_instances.extend(allof_instances)
oneof_instance = get_oneof_instance(self, model_args, constant_args) oneof_instance = get_oneof_instance(self.__class__, model_args, constant_args)
if oneof_instance is not None: if oneof_instance is not None:
composed_instances.append(oneof_instance) composed_instances.append(oneof_instance)
anyof_instances = get_anyof_instances(self, model_args, constant_args) anyof_instances = get_anyof_instances(self, model_args, constant_args)

View File

@@ -27,6 +27,7 @@ docs/ChildCatAllOf.md
docs/ClassModel.md docs/ClassModel.md
docs/Client.md docs/Client.md
docs/ComplexQuadrilateral.md docs/ComplexQuadrilateral.md
docs/ComposedOneOfNumberWithValidations.md
docs/DanishPig.md docs/DanishPig.md
docs/DefaultApi.md docs/DefaultApi.md
docs/Dog.md docs/Dog.md
@@ -135,6 +136,7 @@ petstore_api/model/child_cat_all_of.py
petstore_api/model/class_model.py petstore_api/model/class_model.py
petstore_api/model/client.py petstore_api/model/client.py
petstore_api/model/complex_quadrilateral.py petstore_api/model/complex_quadrilateral.py
petstore_api/model/composed_one_of_number_with_validations.py
petstore_api/model/danish_pig.py petstore_api/model/danish_pig.py
petstore_api/model/dog.py petstore_api/model/dog.py
petstore_api/model/dog_all_of.py petstore_api/model/dog_all_of.py

View File

@@ -86,6 +86,7 @@ Class | Method | HTTP request | Description
*FakeApi* | [**array_model**](docs/FakeApi.md#array_model) | **POST** /fake/refs/arraymodel | *FakeApi* | [**array_model**](docs/FakeApi.md#array_model) | **POST** /fake/refs/arraymodel |
*FakeApi* | [**array_of_enums**](docs/FakeApi.md#array_of_enums) | **POST** /fake/refs/array-of-enums | Array of Enums *FakeApi* | [**array_of_enums**](docs/FakeApi.md#array_of_enums) | **POST** /fake/refs/array-of-enums | Array of Enums
*FakeApi* | [**boolean**](docs/FakeApi.md#boolean) | **POST** /fake/refs/boolean | *FakeApi* | [**boolean**](docs/FakeApi.md#boolean) | **POST** /fake/refs/boolean |
*FakeApi* | [**composed_one_of_number_with_validations**](docs/FakeApi.md#composed_one_of_number_with_validations) | **POST** /fake/refs/composed_one_of_number_with_validations |
*FakeApi* | [**fake_health_get**](docs/FakeApi.md#fake_health_get) | **GET** /fake/health | Health check endpoint *FakeApi* | [**fake_health_get**](docs/FakeApi.md#fake_health_get) | **GET** /fake/health | Health check endpoint
*FakeApi* | [**number_with_validations**](docs/FakeApi.md#number_with_validations) | **POST** /fake/refs/number | *FakeApi* | [**number_with_validations**](docs/FakeApi.md#number_with_validations) | **POST** /fake/refs/number |
*FakeApi* | [**object_model_with_ref_props**](docs/FakeApi.md#object_model_with_ref_props) | **POST** /fake/refs/object_model_with_ref_props | *FakeApi* | [**object_model_with_ref_props**](docs/FakeApi.md#object_model_with_ref_props) | **POST** /fake/refs/object_model_with_ref_props |
@@ -150,6 +151,7 @@ Class | Method | HTTP request | Description
- [class_model.ClassModel](docs/ClassModel.md) - [class_model.ClassModel](docs/ClassModel.md)
- [client.Client](docs/Client.md) - [client.Client](docs/Client.md)
- [complex_quadrilateral.ComplexQuadrilateral](docs/ComplexQuadrilateral.md) - [complex_quadrilateral.ComplexQuadrilateral](docs/ComplexQuadrilateral.md)
- [composed_one_of_number_with_validations.ComposedOneOfNumberWithValidations](docs/ComposedOneOfNumberWithValidations.md)
- [danish_pig.DanishPig](docs/DanishPig.md) - [danish_pig.DanishPig](docs/DanishPig.md)
- [dog.Dog](docs/Dog.md) - [dog.Dog](docs/Dog.md)
- [dog_all_of.DogAllOf](docs/DogAllOf.md) - [dog_all_of.DogAllOf](docs/DogAllOf.md)

View File

@@ -0,0 +1,13 @@
# composed_one_of_number_with_validations.ComposedOneOfNumberWithValidations
this is a model that allows payloads of type object or number
## Properties
Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
**class_name** | **str** | | defaults to nulltype.Null
**color** | **str** | | [optional] if omitted the server will use the default value of 'red'
**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]
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)

View File

@@ -3,7 +3,7 @@
## Properties ## Properties
Name | Type | Description | Notes Name | Type | Description | Notes
------------ | ------------- | ------------- | ------------- ------------ | ------------- | ------------- | -------------
**value** | **str** | | defaults to '-efg' **value** | **str** | | if omitted the server will use the default value of '-efg', must be one of ["_abc", "-efg", "(xyz)", ]
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) [[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)

View File

@@ -7,7 +7,7 @@ Name | Type | Description | Notes
**enum_string** | **str** | | [optional] **enum_string** | **str** | | [optional]
**enum_integer** | **int** | | [optional] **enum_integer** | **int** | | [optional]
**enum_number** | **float** | | [optional] **enum_number** | **float** | | [optional]
**string_enum** | [**string_enum.StringEnum**](StringEnum.md) | | [optional] **string_enum** | [**string_enum.StringEnum, none_type**](StringEnum.md) | | [optional]
**integer_enum** | [**integer_enum.IntegerEnum**](IntegerEnum.md) | | [optional] **integer_enum** | [**integer_enum.IntegerEnum**](IntegerEnum.md) | | [optional]
**string_enum_with_default_value** | [**string_enum_with_default_value.StringEnumWithDefaultValue**](StringEnumWithDefaultValue.md) | | [optional] **string_enum_with_default_value** | [**string_enum_with_default_value.StringEnumWithDefaultValue**](StringEnumWithDefaultValue.md) | | [optional]
**integer_enum_with_default_value** | [**integer_enum_with_default_value.IntegerEnumWithDefaultValue**](IntegerEnumWithDefaultValue.md) | | [optional] **integer_enum_with_default_value** | [**integer_enum_with_default_value.IntegerEnumWithDefaultValue**](IntegerEnumWithDefaultValue.md) | | [optional]

View File

@@ -8,6 +8,7 @@ Method | HTTP request | Description
[**array_model**](FakeApi.md#array_model) | **POST** /fake/refs/arraymodel | [**array_model**](FakeApi.md#array_model) | **POST** /fake/refs/arraymodel |
[**array_of_enums**](FakeApi.md#array_of_enums) | **POST** /fake/refs/array-of-enums | Array of Enums [**array_of_enums**](FakeApi.md#array_of_enums) | **POST** /fake/refs/array-of-enums | Array of Enums
[**boolean**](FakeApi.md#boolean) | **POST** /fake/refs/boolean | [**boolean**](FakeApi.md#boolean) | **POST** /fake/refs/boolean |
[**composed_one_of_number_with_validations**](FakeApi.md#composed_one_of_number_with_validations) | **POST** /fake/refs/composed_one_of_number_with_validations |
[**fake_health_get**](FakeApi.md#fake_health_get) | **GET** /fake/health | Health check endpoint [**fake_health_get**](FakeApi.md#fake_health_get) | **GET** /fake/health | Health check endpoint
[**number_with_validations**](FakeApi.md#number_with_validations) | **POST** /fake/refs/number | [**number_with_validations**](FakeApi.md#number_with_validations) | **POST** /fake/refs/number |
[**object_model_with_ref_props**](FakeApi.md#object_model_with_ref_props) | **POST** /fake/refs/object_model_with_ref_props | [**object_model_with_ref_props**](FakeApi.md#object_model_with_ref_props) | **POST** /fake/refs/object_model_with_ref_props |
@@ -277,6 +278,70 @@ No authorization required
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) [[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
# **composed_one_of_number_with_validations**
> composed_one_of_number_with_validations.ComposedOneOfNumberWithValidations composed_one_of_number_with_validations()
Test serialization of object with $refed properties
### Example
```python
from __future__ import print_function
import time
import petstore_api
from petstore_api.api import fake_api
from petstore_api.model import composed_one_of_number_with_validations
from pprint import pprint
# Defining the host is optional and defaults to http://petstore.swagger.io:80/v2
# See configuration.py for a list of all supported configuration parameters.
configuration = petstore_api.Configuration(
host = "http://petstore.swagger.io:80/v2"
)
# Enter a context with an instance of the API client
with petstore_api.ApiClient() as api_client:
# Create an instance of the API class
api_instance = fake_api.FakeApi(api_client)
composed_one_of_number_with_validations_composed_one_of_number_with_validations = composed_one_of_number_with_validations.ComposedOneOfNumberWithValidations() # composed_one_of_number_with_validations.ComposedOneOfNumberWithValidations | Input model (optional)
# example passing only required values which don't have defaults set
# and optional values
try:
api_response = api_instance.composed_one_of_number_with_validations(composed_one_of_number_with_validations_composed_one_of_number_with_validations=composed_one_of_number_with_validations_composed_one_of_number_with_validations)
pprint(api_response)
except petstore_api.ApiException as e:
print("Exception when calling FakeApi->composed_one_of_number_with_validations: %s\n" % e)
```
### Parameters
Name | Type | Description | Notes
------------- | ------------- | ------------- | -------------
**composed_one_of_number_with_validations_composed_one_of_number_with_validations** | [**composed_one_of_number_with_validations.ComposedOneOfNumberWithValidations**](ComposedOneOfNumberWithValidations.md)| Input model | [optional]
### Return type
[**composed_one_of_number_with_validations.ComposedOneOfNumberWithValidations**](ComposedOneOfNumberWithValidations.md)
### Authorization
No authorization required
### HTTP request headers
- **Content-Type**: application/json
- **Accept**: application/json
### HTTP response details
| Status code | Description | Response headers |
|-------------|-------------|------------------|
**200** | Output model | - |
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
# **fake_health_get** # **fake_health_get**
> health_check_result.HealthCheckResult fake_health_get() > health_check_result.HealthCheckResult fake_health_get()

View File

@@ -3,7 +3,7 @@
## Properties ## Properties
Name | Type | Description | Notes Name | Type | Description | Notes
------------ | ------------- | ------------- | ------------- ------------ | ------------- | ------------- | -------------
**value** | **int** | | **value** | **int** | | must be one of [0, 1, 2, ]
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) [[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)

View File

@@ -3,7 +3,7 @@
## Properties ## Properties
Name | Type | Description | Notes Name | Type | Description | Notes
------------ | ------------- | ------------- | ------------- ------------ | ------------- | ------------- | -------------
**value** | **int** | | defaults to 0 **value** | **int** | | defaults to 0, must be one of [0, ]
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) [[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)

View File

@@ -3,7 +3,7 @@
## Properties ## Properties
Name | Type | Description | Notes Name | Type | Description | Notes
------------ | ------------- | ------------- | ------------- ------------ | ------------- | ------------- | -------------
**value** | **int** | | defaults to 0 **value** | **int** | | if omitted the server will use the default value of 0, must be one of [0, 1, 2, ]
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) [[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)

View File

@@ -3,7 +3,7 @@
## Properties ## Properties
Name | Type | Description | Notes Name | Type | Description | Notes
------------ | ------------- | ------------- | ------------- ------------ | ------------- | ------------- | -------------
**value** | **str, none_type** | | **value** | **str** | | must be one of ["placed", "approved", "delivered", ]
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) [[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)

View File

@@ -3,7 +3,7 @@
## Properties ## Properties
Name | Type | Description | Notes Name | Type | Description | Notes
------------ | ------------- | ------------- | ------------- ------------ | ------------- | ------------- | -------------
**value** | **str** | | defaults to 'placed' **value** | **str** | | if omitted the server will use the default value of 'placed', must be one of ["placed", "approved", "delivered", ]
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) [[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)

View File

@@ -33,6 +33,7 @@ from petstore_api.model_utils import ( # noqa: F401
from petstore_api.model import additional_properties_with_array_of_enums from petstore_api.model import additional_properties_with_array_of_enums
from petstore_api.model import animal_farm from petstore_api.model import animal_farm
from petstore_api.model import array_of_enums from petstore_api.model import array_of_enums
from petstore_api.model import composed_one_of_number_with_validations
from petstore_api.model import health_check_result from petstore_api.model import health_check_result
from petstore_api.model import number_with_validations from petstore_api.model import number_with_validations
from petstore_api.model import object_model_with_ref_props from petstore_api.model import object_model_with_ref_props
@@ -500,6 +501,118 @@ class FakeApi(object):
callable=__boolean callable=__boolean
) )
def __composed_one_of_number_with_validations(
self,
**kwargs
):
"""composed_one_of_number_with_validations # noqa: E501
Test serialization of object with $refed properties # noqa: E501
This method makes a synchronous HTTP request by default. To make an
asynchronous HTTP request, please pass async_req=True
>>> thread = api.composed_one_of_number_with_validations(async_req=True)
>>> result = thread.get()
Keyword Args:
composed_one_of_number_with_validations_composed_one_of_number_with_validations (composed_one_of_number_with_validations.ComposedOneOfNumberWithValidations): Input model. [optional]
_return_http_data_only (bool): response data without head status
code and headers. Default is True.
_preload_content (bool): if False, the urllib3.HTTPResponse object
will be returned without reading/decoding response data.
Default is True.
_request_timeout (float/tuple): timeout setting for this request. If one
number provided, it will be total request timeout. It can also
be a pair (tuple) of (connection, read) timeouts.
Default is None.
_check_input_type (bool): specifies if type checking
should be done one the data sent to the server.
Default is True.
_check_return_type (bool): specifies if type checking
should be done one the data received from the server.
Default is True.
_host_index (int/None): specifies the index of the server
that we want to use.
Default is read from the configuration.
async_req (bool): execute request asynchronously
Returns:
composed_one_of_number_with_validations.ComposedOneOfNumberWithValidations
If the method is called asynchronously, returns the request
thread.
"""
kwargs['async_req'] = kwargs.get(
'async_req', False
)
kwargs['_return_http_data_only'] = kwargs.get(
'_return_http_data_only', True
)
kwargs['_preload_content'] = kwargs.get(
'_preload_content', True
)
kwargs['_request_timeout'] = kwargs.get(
'_request_timeout', None
)
kwargs['_check_input_type'] = kwargs.get(
'_check_input_type', True
)
kwargs['_check_return_type'] = kwargs.get(
'_check_return_type', True
)
kwargs['_host_index'] = kwargs.get('_host_index')
return self.call_with_http_info(**kwargs)
self.composed_one_of_number_with_validations = Endpoint(
settings={
'response_type': (composed_one_of_number_with_validations.ComposedOneOfNumberWithValidations,),
'auth': [],
'endpoint_path': '/fake/refs/composed_one_of_number_with_validations',
'operation_id': 'composed_one_of_number_with_validations',
'http_method': 'POST',
'servers': None,
},
params_map={
'all': [
'composed_one_of_number_with_validations_composed_one_of_number_with_validations',
],
'required': [],
'nullable': [
],
'enum': [
],
'validation': [
]
},
root_map={
'validations': {
},
'allowed_values': {
},
'openapi_types': {
'composed_one_of_number_with_validations_composed_one_of_number_with_validations':
(composed_one_of_number_with_validations.ComposedOneOfNumberWithValidations,),
},
'attribute_map': {
},
'location_map': {
'composed_one_of_number_with_validations_composed_one_of_number_with_validations': 'body',
},
'collection_format_map': {
}
},
headers_map={
'accept': [
'application/json'
],
'content_type': [
'application/json'
]
},
api_client=api_client,
callable=__composed_one_of_number_with_validations
)
def __fake_health_get( def __fake_health_get(
self, self,
**kwargs **kwargs

View File

@@ -81,7 +81,7 @@ class AnimalFarm(ModelSimple):
and the value is attribute type. and the value is attribute type.
""" """
return { return {
'value': ([animal.Animal],), # noqa: E501 'value': ([animal.Animal],),
} }
@cached_property @cached_property
@@ -102,11 +102,11 @@ class AnimalFarm(ModelSimple):
]) ])
@convert_js_args_to_python_args @convert_js_args_to_python_args
def __init__(self, value, *args, **kwargs): # noqa: E501 def __init__(self, value, *args, **kwargs):
"""animal_farm.AnimalFarm - a model defined in OpenAPI """animal_farm.AnimalFarm - a model defined in OpenAPI
Args: Args:
value ([animal.Animal]): value ([animal.Animal]): # noqa: E501
Keyword Args: Keyword Args:
_check_type (bool): if True, values for parameters in openapi_types _check_type (bool): if True, values for parameters in openapi_types
@@ -163,13 +163,13 @@ class AnimalFarm(ModelSimple):
self._path_to_item = _path_to_item self._path_to_item = _path_to_item
self._configuration = _configuration self._configuration = _configuration
self._visited_composed_classes = _visited_composed_classes + (self.__class__,) self._visited_composed_classes = _visited_composed_classes + (self.__class__,)
self.value = value self.value = value
for var_name, var_value in six.iteritems(kwargs): if kwargs:
if var_name not in self.attribute_map and \ raise ApiTypeError(
self._configuration is not None and \ "Invalid named arguments=%s passed to %s. Remove those invalid named arguments." % (
self._configuration.discard_unknown_keys and \ kwargs,
self.additional_properties_type is None: self.__class__.__name__,
# discard variable. ),
continue path_to_item=_path_to_item,
setattr(self, var_name, var_value) valid_classes=(self.__class__,),
)

View File

@@ -81,7 +81,7 @@ class ArrayOfEnums(ModelSimple):
and the value is attribute type. and the value is attribute type.
""" """
return { return {
'value': ([string_enum.StringEnum, none_type],), # noqa: E501 'value': ([string_enum.StringEnum, none_type],),
} }
@cached_property @cached_property
@@ -102,11 +102,11 @@ class ArrayOfEnums(ModelSimple):
]) ])
@convert_js_args_to_python_args @convert_js_args_to_python_args
def __init__(self, value, *args, **kwargs): # noqa: E501 def __init__(self, value, *args, **kwargs):
"""array_of_enums.ArrayOfEnums - a model defined in OpenAPI """array_of_enums.ArrayOfEnums - a model defined in OpenAPI
Args: Args:
value ([string_enum.StringEnum, none_type]): value ([string_enum.StringEnum, none_type]): # noqa: E501
Keyword Args: Keyword Args:
_check_type (bool): if True, values for parameters in openapi_types _check_type (bool): if True, values for parameters in openapi_types
@@ -163,13 +163,13 @@ class ArrayOfEnums(ModelSimple):
self._path_to_item = _path_to_item self._path_to_item = _path_to_item
self._configuration = _configuration self._configuration = _configuration
self._visited_composed_classes = _visited_composed_classes + (self.__class__,) self._visited_composed_classes = _visited_composed_classes + (self.__class__,)
self.value = value self.value = value
for var_name, var_value in six.iteritems(kwargs): if kwargs:
if var_name not in self.attribute_map and \ raise ApiTypeError(
self._configuration is not None and \ "Invalid named arguments=%s passed to %s. Remove those invalid named arguments." % (
self._configuration.discard_unknown_keys and \ kwargs,
self.additional_properties_type is None: self.__class__.__name__,
# discard variable. ),
continue path_to_item=_path_to_item,
setattr(self, var_name, var_value) valid_classes=(self.__class__,),
)

View File

@@ -0,0 +1,239 @@
# coding: utf-8
"""
OpenAPI Petstore
This spec is mainly for testing Petstore server and contains fake endpoints, models. Please do not use this for any other purpose. Special characters: \" \\ # noqa: E501
The version of the OpenAPI document: 1.0.0
Generated by: https://openapi-generator.tech
"""
from __future__ import absolute_import
import re # noqa: F401
import sys # noqa: F401
import six # noqa: F401
import nulltype # noqa: F401
from petstore_api.model_utils import ( # noqa: F401
ApiTypeError,
ModelComposed,
ModelNormal,
ModelSimple,
cached_property,
change_keys_js_to_python,
convert_js_args_to_python_args,
date,
datetime,
file_type,
int,
none_type,
str,
validate_get_composed_info,
)
try:
from petstore_api.model import animal
except ImportError:
animal = sys.modules[
'petstore_api.model.animal']
try:
from petstore_api.model import number_with_validations
except ImportError:
number_with_validations = sys.modules[
'petstore_api.model.number_with_validations']
class ComposedOneOfNumberWithValidations(ModelComposed):
"""NOTE: This class is auto generated by OpenAPI Generator.
Ref: https://openapi-generator.tech
Do not edit the class manually.
Attributes:
allowed_values (dict): The key is the tuple path to the attribute
and the for var_name this is (var_name,). The value is a dict
with a capitalized key describing the allowed value and an allowed
value. These dicts store the allowed enum values.
attribute_map (dict): The key is attribute name
and the value is json key in definition.
discriminator_value_class_map (dict): A dict to go from the discriminator
variable value to the discriminator class name.
validations (dict): The key is the tuple path to the attribute
and the for var_name this is (var_name,). The value is a dict
that stores validations for max_length, min_length, max_items,
min_items, exclusive_maximum, inclusive_maximum, exclusive_minimum,
inclusive_minimum, and regex.
additional_properties_type (tuple): A tuple of classes accepted
as additional properties values.
"""
allowed_values = {
}
validations = {
}
additional_properties_type = (bool, date, datetime, dict, float, int, list, str, none_type,) # noqa: E501
_nullable = False
@cached_property
def openapi_types():
"""
This must be a class method so a model may have properties that are
of type self, this ensures that we don't create a cyclic import
Returns
openapi_types (dict): The key is attribute name
and the value is attribute type.
"""
return {
'class_name': (str,), # noqa: E501
'color': (str,), # noqa: E501
}
@cached_property
def discriminator():
return None
attribute_map = {
'class_name': 'className', # noqa: E501
'color': 'color', # noqa: E501
}
required_properties = set([
'_data_store',
'_check_type',
'_spec_property_naming',
'_path_to_item',
'_configuration',
'_visited_composed_classes',
'_composed_instances',
'_var_name_to_model_instances',
'_additional_properties_model_instances',
])
@convert_js_args_to_python_args
def __init__(self, *args, **kwargs): # noqa: E501
"""composed_one_of_number_with_validations.ComposedOneOfNumberWithValidations - a model defined in OpenAPI
Args:
Keyword Args:
class_name (str): defaults to nulltype.Null # noqa: E501
_check_type (bool): if True, values for parameters in openapi_types
will be type checked and a TypeError will be
raised if the wrong type is input.
Defaults to True
_path_to_item (tuple/list): This is a list of keys or values to
drill down to the model in received_data
when deserializing a response
_spec_property_naming (bool): True if the variable names in the input data
are serialized names, as specified in the OpenAPI document.
False if the variable names in the input data
are pythonic names, e.g. snake case (default)
_configuration (Configuration): the instance to use when
deserializing a file_type parameter.
If passed, type conversion is attempted
If omitted no type conversion is done.
_visited_composed_classes (tuple): This stores a tuple of
classes that we have traveled through so that
if we see that class again we will not use its
discriminator again.
When traveling through a discriminator, the
composed schema that is
is traveled through is added to this set.
For example if Animal has a discriminator
petType and we pass in "Dog", and the class Dog
allOf includes Animal, we move through Animal
once using the discriminator, and pick Dog.
Then in Dog, we will make an instance of the
Animal class but this time we won't travel
through its discriminator because we passed in
_visited_composed_classes = (Animal,)
color (str): [optional] if omitted the server will use the default value of 'red' # noqa: E501
"""
class_name = kwargs.get('class_name', nulltype.Null)
_check_type = kwargs.pop('_check_type', True)
_spec_property_naming = kwargs.pop('_spec_property_naming', False)
_path_to_item = kwargs.pop('_path_to_item', ())
_configuration = kwargs.pop('_configuration', None)
_visited_composed_classes = kwargs.pop('_visited_composed_classes', ())
if args:
raise ApiTypeError(
"Invalid positional arguments=%s passed to %s. Remove those invalid positional arguments." % (
args,
self.__class__.__name__,
),
path_to_item=_path_to_item,
valid_classes=(self.__class__,),
)
self._data_store = {}
self._check_type = _check_type
self._spec_property_naming = _spec_property_naming
self._path_to_item = _path_to_item
self._configuration = _configuration
self._visited_composed_classes = _visited_composed_classes + (self.__class__,)
constant_args = {
'_check_type': _check_type,
'_path_to_item': _path_to_item,
'_spec_property_naming': _spec_property_naming,
'_configuration': _configuration,
'_visited_composed_classes': self._visited_composed_classes,
}
required_args = {
'class_name': class_name,
}
# remove args whose value is Null because they are unset
required_arg_names = list(required_args.keys())
for required_arg_name in required_arg_names:
if required_args[required_arg_name] is nulltype.Null:
del required_args[required_arg_name]
model_args = {}
model_args.update(required_args)
model_args.update(kwargs)
composed_info = validate_get_composed_info(
constant_args, model_args, self)
self._composed_instances = composed_info[0]
self._var_name_to_model_instances = composed_info[1]
self._additional_properties_model_instances = composed_info[2]
unused_args = composed_info[3]
for var_name, var_value in required_args.items():
setattr(self, var_name, var_value)
for var_name, var_value in six.iteritems(kwargs):
if var_name in unused_args and \
self._configuration is not None and \
self._configuration.discard_unknown_keys and \
not self._additional_properties_model_instances:
# discard variable.
continue
setattr(self, var_name, var_value)
@cached_property
def _composed_schemas():
# we need this here to make our import statements work
# we must store _composed_schemas in here so the code is only run
# when we invoke this method. If we kept this at the class
# level we would get an error beause the class level
# code would be run when this module is imported, and these composed
# classes don't exist yet because their module has not finished
# loading
return {
'anyOf': [
],
'allOf': [
],
'oneOf': [
animal.Animal,
date,
none_type,
number_with_validations.NumberWithValidations,
],
}

View File

@@ -81,7 +81,7 @@ class EnumClass(ModelSimple):
and the value is attribute type. and the value is attribute type.
""" """
return { return {
'value': (str,), # noqa: E501 'value': (str,),
} }
@cached_property @cached_property
@@ -102,13 +102,13 @@ class EnumClass(ModelSimple):
]) ])
@convert_js_args_to_python_args @convert_js_args_to_python_args
def __init__(self, *args, **kwargs): # noqa: E501 def __init__(self, value, *args, **kwargs):
"""enum_class.EnumClass - a model defined in OpenAPI """enum_class.EnumClass - a model defined in OpenAPI
Args: Args:
value (str): if omitted the server will use the default value of '-efg', must be one of ["_abc", "-efg", "(xyz)", ] # noqa: E501
Keyword Args: Keyword Args:
value (str): defaults to '-efg', must be one of ["_abc", "-efg", "(xyz)", ] # noqa: E501
_check_type (bool): if True, values for parameters in openapi_types _check_type (bool): if True, values for parameters in openapi_types
will be type checked and a TypeError will be will be type checked and a TypeError will be
raised if the wrong type is input. raised if the wrong type is input.
@@ -141,7 +141,6 @@ class EnumClass(ModelSimple):
_visited_composed_classes = (Animal,) _visited_composed_classes = (Animal,)
""" """
value = kwargs.get('value', '-efg')
_check_type = kwargs.pop('_check_type', True) _check_type = kwargs.pop('_check_type', True)
_spec_property_naming = kwargs.pop('_spec_property_naming', False) _spec_property_naming = kwargs.pop('_spec_property_naming', False)
_path_to_item = kwargs.pop('_path_to_item', ()) _path_to_item = kwargs.pop('_path_to_item', ())
@@ -164,13 +163,13 @@ class EnumClass(ModelSimple):
self._path_to_item = _path_to_item self._path_to_item = _path_to_item
self._configuration = _configuration self._configuration = _configuration
self._visited_composed_classes = _visited_composed_classes + (self.__class__,) self._visited_composed_classes = _visited_composed_classes + (self.__class__,)
self.value = value self.value = value
for var_name, var_value in six.iteritems(kwargs): if kwargs:
if var_name not in self.attribute_map and \ raise ApiTypeError(
self._configuration is not None and \ "Invalid named arguments=%s passed to %s. Remove those invalid named arguments." % (
self._configuration.discard_unknown_keys and \ kwargs,
self.additional_properties_type is None: self.__class__.__name__,
# discard variable. ),
continue path_to_item=_path_to_item,
setattr(self, var_name, var_value) valid_classes=(self.__class__,),
)

View File

@@ -127,7 +127,7 @@ class EnumTest(ModelNormal):
'enum_string': (str,), # noqa: E501 'enum_string': (str,), # noqa: E501
'enum_integer': (int,), # noqa: E501 'enum_integer': (int,), # noqa: E501
'enum_number': (float,), # noqa: E501 'enum_number': (float,), # noqa: E501
'string_enum': (string_enum.StringEnum,), # noqa: E501 'string_enum': (string_enum.StringEnum, none_type,), # noqa: E501
'integer_enum': (integer_enum.IntegerEnum,), # noqa: E501 'integer_enum': (integer_enum.IntegerEnum,), # noqa: E501
'string_enum_with_default_value': (string_enum_with_default_value.StringEnumWithDefaultValue,), # noqa: E501 'string_enum_with_default_value': (string_enum_with_default_value.StringEnumWithDefaultValue,), # noqa: E501
'integer_enum_with_default_value': (integer_enum_with_default_value.IntegerEnumWithDefaultValue,), # noqa: E501 'integer_enum_with_default_value': (integer_enum_with_default_value.IntegerEnumWithDefaultValue,), # noqa: E501
@@ -202,7 +202,7 @@ class EnumTest(ModelNormal):
enum_string (str): [optional] # noqa: E501 enum_string (str): [optional] # noqa: E501
enum_integer (int): [optional] # noqa: E501 enum_integer (int): [optional] # noqa: E501
enum_number (float): [optional] # noqa: E501 enum_number (float): [optional] # noqa: E501
string_enum (string_enum.StringEnum): [optional] # noqa: E501 string_enum (string_enum.StringEnum, none_type): [optional] # noqa: E501
integer_enum (integer_enum.IntegerEnum): [optional] # noqa: E501 integer_enum (integer_enum.IntegerEnum): [optional] # noqa: E501
string_enum_with_default_value (string_enum_with_default_value.StringEnumWithDefaultValue): [optional] # noqa: E501 string_enum_with_default_value (string_enum_with_default_value.StringEnumWithDefaultValue): [optional] # noqa: E501
integer_enum_with_default_value (integer_enum_with_default_value.IntegerEnumWithDefaultValue): [optional] # noqa: E501 integer_enum_with_default_value (integer_enum_with_default_value.IntegerEnumWithDefaultValue): [optional] # noqa: E501

View File

@@ -81,7 +81,7 @@ class IntegerEnum(ModelSimple):
and the value is attribute type. and the value is attribute type.
""" """
return { return {
'value': (int,), # noqa: E501 'value': (int,),
} }
@cached_property @cached_property
@@ -102,11 +102,11 @@ class IntegerEnum(ModelSimple):
]) ])
@convert_js_args_to_python_args @convert_js_args_to_python_args
def __init__(self, value, *args, **kwargs): # noqa: E501 def __init__(self, value, *args, **kwargs):
"""integer_enum.IntegerEnum - a model defined in OpenAPI """integer_enum.IntegerEnum - a model defined in OpenAPI
Args: Args:
value (int): value (int):, must be one of [0, 1, 2, ] # noqa: E501
Keyword Args: Keyword Args:
_check_type (bool): if True, values for parameters in openapi_types _check_type (bool): if True, values for parameters in openapi_types
@@ -163,13 +163,13 @@ class IntegerEnum(ModelSimple):
self._path_to_item = _path_to_item self._path_to_item = _path_to_item
self._configuration = _configuration self._configuration = _configuration
self._visited_composed_classes = _visited_composed_classes + (self.__class__,) self._visited_composed_classes = _visited_composed_classes + (self.__class__,)
self.value = value self.value = value
for var_name, var_value in six.iteritems(kwargs): if kwargs:
if var_name not in self.attribute_map and \ raise ApiTypeError(
self._configuration is not None and \ "Invalid named arguments=%s passed to %s. Remove those invalid named arguments." % (
self._configuration.discard_unknown_keys and \ kwargs,
self.additional_properties_type is None: self.__class__.__name__,
# discard variable. ),
continue path_to_item=_path_to_item,
setattr(self, var_name, var_value) valid_classes=(self.__class__,),
)

View File

@@ -79,7 +79,7 @@ class IntegerEnumOneValue(ModelSimple):
and the value is attribute type. and the value is attribute type.
""" """
return { return {
'value': (int,), # noqa: E501 'value': (int,),
} }
@cached_property @cached_property
@@ -100,11 +100,9 @@ class IntegerEnumOneValue(ModelSimple):
]) ])
@convert_js_args_to_python_args @convert_js_args_to_python_args
def __init__(self, *args, **kwargs): # noqa: E501 def __init__(self, *args, **kwargs):
"""integer_enum_one_value.IntegerEnumOneValue - a model defined in OpenAPI """integer_enum_one_value.IntegerEnumOneValue - a model defined in OpenAPI
Args:
Keyword Args: Keyword Args:
value (int): defaults to 0, must be one of [0, ] # noqa: E501 value (int): defaults to 0, must be one of [0, ] # noqa: E501
_check_type (bool): if True, values for parameters in openapi_types _check_type (bool): if True, values for parameters in openapi_types
@@ -139,7 +137,13 @@ class IntegerEnumOneValue(ModelSimple):
_visited_composed_classes = (Animal,) _visited_composed_classes = (Animal,)
""" """
value = kwargs.get('value', 0) if 'value' in kwargs:
value = kwargs.pop('value')
elif args:
args = list(args)
value = args.pop(0)
else:
value = 0
_check_type = kwargs.pop('_check_type', True) _check_type = kwargs.pop('_check_type', True)
_spec_property_naming = kwargs.pop('_spec_property_naming', False) _spec_property_naming = kwargs.pop('_spec_property_naming', False)
_path_to_item = kwargs.pop('_path_to_item', ()) _path_to_item = kwargs.pop('_path_to_item', ())
@@ -162,13 +166,13 @@ class IntegerEnumOneValue(ModelSimple):
self._path_to_item = _path_to_item self._path_to_item = _path_to_item
self._configuration = _configuration self._configuration = _configuration
self._visited_composed_classes = _visited_composed_classes + (self.__class__,) self._visited_composed_classes = _visited_composed_classes + (self.__class__,)
self.value = value self.value = value
for var_name, var_value in six.iteritems(kwargs): if kwargs:
if var_name not in self.attribute_map and \ raise ApiTypeError(
self._configuration is not None and \ "Invalid named arguments=%s passed to %s. Remove those invalid named arguments." % (
self._configuration.discard_unknown_keys and \ kwargs,
self.additional_properties_type is None: self.__class__.__name__,
# discard variable. ),
continue path_to_item=_path_to_item,
setattr(self, var_name, var_value) valid_classes=(self.__class__,),
)

View File

@@ -81,7 +81,7 @@ class IntegerEnumWithDefaultValue(ModelSimple):
and the value is attribute type. and the value is attribute type.
""" """
return { return {
'value': (int,), # noqa: E501 'value': (int,),
} }
@cached_property @cached_property
@@ -102,13 +102,13 @@ class IntegerEnumWithDefaultValue(ModelSimple):
]) ])
@convert_js_args_to_python_args @convert_js_args_to_python_args
def __init__(self, *args, **kwargs): # noqa: E501 def __init__(self, value, *args, **kwargs):
"""integer_enum_with_default_value.IntegerEnumWithDefaultValue - a model defined in OpenAPI """integer_enum_with_default_value.IntegerEnumWithDefaultValue - a model defined in OpenAPI
Args: Args:
value (int): if omitted the server will use the default value of 0, must be one of [0, 1, 2, ] # noqa: E501
Keyword Args: Keyword Args:
value (int): defaults to 0, must be one of [0, 1, 2, ] # noqa: E501
_check_type (bool): if True, values for parameters in openapi_types _check_type (bool): if True, values for parameters in openapi_types
will be type checked and a TypeError will be will be type checked and a TypeError will be
raised if the wrong type is input. raised if the wrong type is input.
@@ -141,7 +141,6 @@ class IntegerEnumWithDefaultValue(ModelSimple):
_visited_composed_classes = (Animal,) _visited_composed_classes = (Animal,)
""" """
value = kwargs.get('value', 0)
_check_type = kwargs.pop('_check_type', True) _check_type = kwargs.pop('_check_type', True)
_spec_property_naming = kwargs.pop('_spec_property_naming', False) _spec_property_naming = kwargs.pop('_spec_property_naming', False)
_path_to_item = kwargs.pop('_path_to_item', ()) _path_to_item = kwargs.pop('_path_to_item', ())
@@ -164,13 +163,13 @@ class IntegerEnumWithDefaultValue(ModelSimple):
self._path_to_item = _path_to_item self._path_to_item = _path_to_item
self._configuration = _configuration self._configuration = _configuration
self._visited_composed_classes = _visited_composed_classes + (self.__class__,) self._visited_composed_classes = _visited_composed_classes + (self.__class__,)
self.value = value self.value = value
for var_name, var_value in six.iteritems(kwargs): if kwargs:
if var_name not in self.attribute_map and \ raise ApiTypeError(
self._configuration is not None and \ "Invalid named arguments=%s passed to %s. Remove those invalid named arguments." % (
self._configuration.discard_unknown_keys and \ kwargs,
self.additional_properties_type is None: self.__class__.__name__,
# discard variable. ),
continue path_to_item=_path_to_item,
setattr(self, var_name, var_value) valid_classes=(self.__class__,),
)

View File

@@ -80,7 +80,7 @@ class NumberWithValidations(ModelSimple):
and the value is attribute type. and the value is attribute type.
""" """
return { return {
'value': (float,), # noqa: E501 'value': (float,),
} }
@cached_property @cached_property
@@ -101,11 +101,11 @@ class NumberWithValidations(ModelSimple):
]) ])
@convert_js_args_to_python_args @convert_js_args_to_python_args
def __init__(self, value, *args, **kwargs): # noqa: E501 def __init__(self, value, *args, **kwargs):
"""number_with_validations.NumberWithValidations - a model defined in OpenAPI """number_with_validations.NumberWithValidations - a model defined in OpenAPI
Args: Args:
value (float): value (float): # noqa: E501
Keyword Args: Keyword Args:
_check_type (bool): if True, values for parameters in openapi_types _check_type (bool): if True, values for parameters in openapi_types
@@ -162,13 +162,13 @@ class NumberWithValidations(ModelSimple):
self._path_to_item = _path_to_item self._path_to_item = _path_to_item
self._configuration = _configuration self._configuration = _configuration
self._visited_composed_classes = _visited_composed_classes + (self.__class__,) self._visited_composed_classes = _visited_composed_classes + (self.__class__,)
self.value = value self.value = value
for var_name, var_value in six.iteritems(kwargs): if kwargs:
if var_name not in self.attribute_map and \ raise ApiTypeError(
self._configuration is not None and \ "Invalid named arguments=%s passed to %s. Remove those invalid named arguments." % (
self._configuration.discard_unknown_keys and \ kwargs,
self.additional_properties_type is None: self.__class__.__name__,
# discard variable. ),
continue path_to_item=_path_to_item,
setattr(self, var_name, var_value) valid_classes=(self.__class__,),
)

View File

@@ -82,7 +82,7 @@ class StringEnum(ModelSimple):
and the value is attribute type. and the value is attribute type.
""" """
return { return {
'value': (str, none_type,), # noqa: E501 'value': (str,),
} }
@cached_property @cached_property
@@ -103,11 +103,11 @@ class StringEnum(ModelSimple):
]) ])
@convert_js_args_to_python_args @convert_js_args_to_python_args
def __init__(self, value, *args, **kwargs): # noqa: E501 def __init__(self, value, *args, **kwargs):
"""string_enum.StringEnum - a model defined in OpenAPI """string_enum.StringEnum - a model defined in OpenAPI
Args: Args:
value (str, none_type): value (str):, must be one of ["placed", "approved", "delivered", ] # noqa: E501
Keyword Args: Keyword Args:
_check_type (bool): if True, values for parameters in openapi_types _check_type (bool): if True, values for parameters in openapi_types
@@ -164,13 +164,13 @@ class StringEnum(ModelSimple):
self._path_to_item = _path_to_item self._path_to_item = _path_to_item
self._configuration = _configuration self._configuration = _configuration
self._visited_composed_classes = _visited_composed_classes + (self.__class__,) self._visited_composed_classes = _visited_composed_classes + (self.__class__,)
self.value = value self.value = value
for var_name, var_value in six.iteritems(kwargs): if kwargs:
if var_name not in self.attribute_map and \ raise ApiTypeError(
self._configuration is not None and \ "Invalid named arguments=%s passed to %s. Remove those invalid named arguments." % (
self._configuration.discard_unknown_keys and \ kwargs,
self.additional_properties_type is None: self.__class__.__name__,
# discard variable. ),
continue path_to_item=_path_to_item,
setattr(self, var_name, var_value) valid_classes=(self.__class__,),
)

View File

@@ -81,7 +81,7 @@ class StringEnumWithDefaultValue(ModelSimple):
and the value is attribute type. and the value is attribute type.
""" """
return { return {
'value': (str,), # noqa: E501 'value': (str,),
} }
@cached_property @cached_property
@@ -102,13 +102,13 @@ class StringEnumWithDefaultValue(ModelSimple):
]) ])
@convert_js_args_to_python_args @convert_js_args_to_python_args
def __init__(self, *args, **kwargs): # noqa: E501 def __init__(self, value, *args, **kwargs):
"""string_enum_with_default_value.StringEnumWithDefaultValue - a model defined in OpenAPI """string_enum_with_default_value.StringEnumWithDefaultValue - a model defined in OpenAPI
Args: Args:
value (str): if omitted the server will use the default value of 'placed', must be one of ["placed", "approved", "delivered", ] # noqa: E501
Keyword Args: Keyword Args:
value (str): defaults to 'placed', must be one of ["placed", "approved", "delivered", ] # noqa: E501
_check_type (bool): if True, values for parameters in openapi_types _check_type (bool): if True, values for parameters in openapi_types
will be type checked and a TypeError will be will be type checked and a TypeError will be
raised if the wrong type is input. raised if the wrong type is input.
@@ -141,7 +141,6 @@ class StringEnumWithDefaultValue(ModelSimple):
_visited_composed_classes = (Animal,) _visited_composed_classes = (Animal,)
""" """
value = kwargs.get('value', 'placed')
_check_type = kwargs.pop('_check_type', True) _check_type = kwargs.pop('_check_type', True)
_spec_property_naming = kwargs.pop('_spec_property_naming', False) _spec_property_naming = kwargs.pop('_spec_property_naming', False)
_path_to_item = kwargs.pop('_path_to_item', ()) _path_to_item = kwargs.pop('_path_to_item', ())
@@ -164,13 +163,13 @@ class StringEnumWithDefaultValue(ModelSimple):
self._path_to_item = _path_to_item self._path_to_item = _path_to_item
self._configuration = _configuration self._configuration = _configuration
self._visited_composed_classes = _visited_composed_classes + (self.__class__,) self._visited_composed_classes = _visited_composed_classes + (self.__class__,)
self.value = value self.value = value
for var_name, var_value in six.iteritems(kwargs): if kwargs:
if var_name not in self.attribute_map and \ raise ApiTypeError(
self._configuration is not None and \ "Invalid named arguments=%s passed to %s. Remove those invalid named arguments." % (
self._configuration.discard_unknown_keys and \ kwargs,
self.additional_properties_type is None: self.__class__.__name__,
# discard variable. ),
continue path_to_item=_path_to_item,
setattr(self, var_name, var_value) valid_classes=(self.__class__,),
)

View File

@@ -62,6 +62,58 @@ class cached_property(object):
return result return result
PRIMITIVE_TYPES = (list, float, int, bool, datetime, date, str, file_type)
def allows_single_value_input(cls):
"""
This function returns True if the input composed schema model or any
descendant model allows a value only input
This is true for cases where oneOf contains items like:
oneOf:
- float
- NumberWithValidation
- StringEnum
- ArrayModel
- null
TODO: lru_cache this
"""
if (
issubclass(cls, ModelSimple) or
cls in PRIMITIVE_TYPES
):
return True
elif issubclass(cls, ModelComposed):
if not cls._composed_schemas['oneOf']:
return False
return any(allows_single_value_input(c) for c in cls._composed_schemas['oneOf'])
return False
def composed_model_input_classes(cls):
"""
This function returns a list of the possible models that can be accepted as
inputs.
TODO: lru_cache this
"""
if issubclass(cls, ModelSimple) or cls in PRIMITIVE_TYPES:
return [cls]
elif issubclass(cls, ModelNormal):
if cls.discriminator is None:
return [cls]
else:
return get_discriminated_classes(cls)
elif issubclass(cls, ModelComposed):
if not cls._composed_schemas['oneOf']:
return []
if cls.discriminator is None:
input_classes = []
for c in cls._composed_schemas['oneOf']:
input_classes.extend(composed_model_input_classes(c))
return input_classes
else:
return get_discriminated_classes(cls)
return []
class OpenApiModel(object): class OpenApiModel(object):
"""The base class for all OpenAPIModels""" """The base class for all OpenAPIModels"""
@@ -138,10 +190,18 @@ class OpenApiModel(object):
# pick a new schema/class to instantiate because a discriminator # pick a new schema/class to instantiate because a discriminator
# propertyName value was passed in # propertyName value was passed in
if len(args) == 1 and args[0] is None and is_type_nullable(cls): if len(args) == 1:
arg = args[0]
if arg is None and is_type_nullable(cls):
# The input data is the 'null' value and the type is nullable. # The input data is the 'null' value and the type is nullable.
return None return None
if issubclass(cls, ModelComposed) and allows_single_value_input(cls):
model_kwargs = {}
oneof_instance = get_oneof_instance(cls, model_kwargs, kwargs, model_arg=arg)
return oneof_instance
visited_composed_classes = kwargs.get('_visited_composed_classes', ()) visited_composed_classes = kwargs.get('_visited_composed_classes', ())
if ( if (
cls.discriminator is None or cls.discriminator is None or
@@ -508,6 +568,10 @@ UPCONVERSION_TYPE_PAIRS = (
(int, float), # A float may be serialized as an integer, e.g. '3' is a valid serialized float. (int, float), # A float may be serialized as an integer, e.g. '3' is a valid serialized float.
(list, ModelComposed), (list, ModelComposed),
(dict, ModelComposed), (dict, ModelComposed),
(str, ModelComposed),
(int, ModelComposed),
(float, ModelComposed),
(list, ModelComposed),
(list, ModelNormal), (list, ModelNormal),
(dict, ModelNormal), (dict, ModelNormal),
(str, ModelSimple), (str, ModelSimple),
@@ -886,20 +950,53 @@ def remove_uncoercible(required_types_classes, current_item, spec_property_namin
results_classes.append(required_type_class) results_classes.append(required_type_class)
return results_classes return results_classes
def get_discriminated_classes(cls):
"""
Returns all the classes that a discriminator converts to
TODO: lru_cache this
"""
possible_classes = []
key = list(cls.discriminator.keys())[0]
if is_type_nullable(cls):
possible_classes.append(cls)
for discr_cls in cls.discriminator[key].values():
if hasattr(discr_cls, 'discriminator') and discr_cls.discriminator is not None:
possible_classes.extend(get_discriminated_classes(discr_cls))
else:
possible_classes.append(discr_cls)
return possible_classes
def get_required_type_classes(required_types_mixed):
def get_possible_classes(cls, from_server_context):
# TODO: lru_cache this
possible_classes = [cls]
if from_server_context:
return possible_classes
if hasattr(cls, 'discriminator') and cls.discriminator is not None:
possible_classes = []
possible_classes.extend(get_discriminated_classes(cls))
elif issubclass(cls, ModelComposed):
possible_classes.extend(composed_model_input_classes(cls))
return possible_classes
def get_required_type_classes(required_types_mixed, spec_property_naming):
"""Converts the tuple required_types into a tuple and a dict described """Converts the tuple required_types into a tuple and a dict described
below below
Args: Args:
required_types_mixed (tuple/list): will contain either classes or required_types_mixed (tuple/list): will contain either classes or
instance of list or dict instance of list or dict
spec_property_naming (bool): if True these values came from the
server, and we use the data types in our endpoints.
If False, we are client side and we need to include
oneOf and discriminator classes inside the data types in our endpoints
Returns: Returns:
(valid_classes, dict_valid_class_to_child_types_mixed): (valid_classes, dict_valid_class_to_child_types_mixed):
valid_classes (tuple): the valid classes that the current item valid_classes (tuple): the valid classes that the current item
should be should be
dict_valid_class_to_child_types_mixed (doct): dict_valid_class_to_child_types_mixed (dict):
valid_class (class): this is the key valid_class (class): this is the key
child_types_mixed (list/dict/tuple): describes the valid child child_types_mixed (list/dict/tuple): describes the valid child
types types
@@ -917,7 +1014,7 @@ def get_required_type_classes(required_types_mixed):
valid_classes.append(dict) valid_classes.append(dict)
child_req_types_by_current_type[dict] = required_type[str] child_req_types_by_current_type[dict] = required_type[str]
else: else:
valid_classes.append(required_type) valid_classes.extend(get_possible_classes(required_type, spec_property_naming))
return tuple(valid_classes), child_req_types_by_current_type return tuple(valid_classes), child_req_types_by_current_type
@@ -1070,7 +1167,7 @@ def deserialize_model(model_data, model_class, path_to_item, check_type,
"""Deserializes model_data to model instance. """Deserializes model_data to model instance.
Args: Args:
model_data (list/dict): data to instantiate the model model_data (int/str/float/bool/none_type/list/dict): data to instantiate the model
model_class (OpenApiModel): the model class model_class (OpenApiModel): the model class
path_to_item (list): path to the model in the received data path_to_item (list): path to the model in the received data
check_type (bool): whether to check the data tupe for the values in check_type (bool): whether to check the data tupe for the values in
@@ -1096,14 +1193,14 @@ def deserialize_model(model_data, model_class, path_to_item, check_type,
_spec_property_naming=spec_property_naming) _spec_property_naming=spec_property_naming)
if issubclass(model_class, ModelSimple): if issubclass(model_class, ModelSimple):
instance = model_class(value=model_data, **kw_args) return model_class(model_data, **kw_args)
return instance elif isinstance(model_data, list):
if isinstance(model_data, list): return model_class(*model_data, **kw_args)
instance = model_class(*model_data, **kw_args)
if isinstance(model_data, dict): if isinstance(model_data, dict):
kw_args.update(model_data) kw_args.update(model_data)
instance = model_class(**kw_args) return model_class(**kw_args)
return instance elif isinstance(model_data, PRIMITIVE_TYPES):
return model_class(model_data, **kw_args)
def deserialize_file(response_data, configuration, content_disposition=None): def deserialize_file(response_data, configuration, content_disposition=None):
@@ -1286,7 +1383,7 @@ def validate_and_convert_types(input_value, required_types_mixed, path_to_item,
Raises: Raises:
ApiTypeError ApiTypeError
""" """
results = get_required_type_classes(required_types_mixed) results = get_required_type_classes(required_types_mixed, spec_property_naming)
valid_classes, child_req_types_by_current_type = results valid_classes, child_req_types_by_current_type = results
input_class_simple = get_simple_class(input_value) input_class_simple = get_simple_class(input_value)
@@ -1523,7 +1620,7 @@ def get_allof_instances(self, model_args, constant_args):
return composed_instances return composed_instances
def get_oneof_instance(self, model_args, constant_args): def get_oneof_instance(cls, model_kwargs, constant_kwargs, model_arg=None):
""" """
Find the oneOf schema that matches the input data (e.g. payload). Find the oneOf schema that matches the input data (e.g. payload).
If exactly one schema matches the input data, an instance of that schema If exactly one schema matches the input data, an instance of that schema
@@ -1531,25 +1628,33 @@ def get_oneof_instance(self, model_args, constant_args):
If zero or more than one schema match the input data, an exception is raised. If zero or more than one schema match the input data, an exception is raised.
In OAS 3.x, the payload MUST, by validation, match exactly one of the In OAS 3.x, the payload MUST, by validation, match exactly one of the
schemas described by oneOf. schemas described by oneOf.
Args: Args:
self: the class we are handling cls: the class we are handling
model_args (dict): var_name to var_value model_kwargs (dict): var_name to var_value
The input data, e.g. the payload that must match a oneOf schema The input data, e.g. the payload that must match a oneOf schema
in the OpenAPI document. in the OpenAPI document.
constant_args (dict): var_name to var_value constant_kwargs (dict): var_name to var_value
args that every model requires, including configuration, server args that every model requires, including configuration, server
and path to item. and path to item.
Kwargs:
model_arg: (int, float, bool, str, date, datetime, ModelSimple, None):
the value to assign to a primitive class or ModelSimple class
Notes:
- this is only passed in when oneOf includes types which are not object
- None is used to suppress handling of model_arg, nullable models are handled in __new__
Returns Returns
oneof_instance (instance) oneof_instance (instance)
""" """
if len(self._composed_schemas['oneOf']) == 0: if len(cls._composed_schemas['oneOf']) == 0:
return None return None
oneof_instances = [] oneof_instances = []
# Iterate over each oneOf schema and determine if the input data # Iterate over each oneOf schema and determine if the input data
# matches the oneOf schemas. # matches the oneOf schemas.
for oneof_class in self._composed_schemas['oneOf']: for oneof_class in cls._composed_schemas['oneOf']:
# The composed oneOf schema allows the 'null' type and the input data # The composed oneOf schema allows the 'null' type and the input data
# is the null value. This is a OAS >= 3.1 feature. # is the null value. This is a OAS >= 3.1 feature.
if oneof_class is none_type: if oneof_class is none_type:
@@ -1557,9 +1662,12 @@ def get_oneof_instance(self, model_args, constant_args):
# none_type deserialization is handled in the __new__ method # none_type deserialization is handled in the __new__ method
continue continue
single_value_input = allows_single_value_input(oneof_class)
if not single_value_input:
# transform js keys from input data to python keys in fixed_model_args # transform js keys from input data to python keys in fixed_model_args
fixed_model_args = change_keys_js_to_python( fixed_model_args = change_keys_js_to_python(
model_args, oneof_class) model_kwargs, oneof_class)
# Extract a dict with the properties that are declared in the oneOf schema. # Extract a dict with the properties that are declared in the oneOf schema.
# Undeclared properties (e.g. properties that are allowed because of the # Undeclared properties (e.g. properties that are allowed because of the
@@ -1576,9 +1684,23 @@ def get_oneof_instance(self, model_args, constant_args):
continue continue
# and use it to make the instance # and use it to make the instance
kwargs.update(constant_args) kwargs.update(constant_kwargs)
try: try:
if not single_value_input:
oneof_instance = oneof_class(**kwargs) oneof_instance = oneof_class(**kwargs)
else:
if issubclass(oneof_class, ModelSimple):
oneof_instance = oneof_class(model_arg, **constant_kwargs)
elif oneof_class in PRIMITIVE_TYPES:
oneof_instance = validate_and_convert_types(
model_arg,
(oneof_class,),
constant_kwargs['_path_to_item'],
constant_kwargs['_spec_property_naming'],
constant_kwargs['_check_type'],
configuration=constant_kwargs['_configuration']
)
oneof_instances.append(oneof_instance) oneof_instances.append(oneof_instance)
except Exception: except Exception:
pass pass
@@ -1586,13 +1708,13 @@ def get_oneof_instance(self, model_args, constant_args):
raise ApiValueError( raise ApiValueError(
"Invalid inputs given to generate an instance of %s. None " "Invalid inputs given to generate an instance of %s. None "
"of the oneOf schemas matched the input data." % "of the oneOf schemas matched the input data." %
self.__class__.__name__ cls.__name__
) )
elif len(oneof_instances) > 1: elif len(oneof_instances) > 1:
raise ApiValueError( raise ApiValueError(
"Invalid inputs given to generate an instance of %s. Multiple " "Invalid inputs given to generate an instance of %s. Multiple "
"oneOf schemas matched the inputs, but a max of one is allowed." % "oneOf schemas matched the inputs, but a max of one is allowed." %
self.__class__.__name__ cls.__name__
) )
return oneof_instances[0] return oneof_instances[0]
@@ -1732,7 +1854,7 @@ def validate_get_composed_info(constant_args, model_args, self):
composed_instances = [] composed_instances = []
allof_instances = get_allof_instances(self, model_args, constant_args) allof_instances = get_allof_instances(self, model_args, constant_args)
composed_instances.extend(allof_instances) composed_instances.extend(allof_instances)
oneof_instance = get_oneof_instance(self, model_args, constant_args) oneof_instance = get_oneof_instance(self.__class__, model_args, constant_args)
if oneof_instance is not None: if oneof_instance is not None:
composed_instances.append(oneof_instance) composed_instances.append(oneof_instance)
anyof_instances = get_anyof_instances(self, model_args, constant_args) anyof_instances = get_anyof_instances(self, model_args, constant_args)

View File

@@ -35,6 +35,7 @@ from petstore_api.model.child_cat_all_of import ChildCatAllOf
from petstore_api.model.class_model import ClassModel from petstore_api.model.class_model import ClassModel
from petstore_api.model.client import Client from petstore_api.model.client import Client
from petstore_api.model.complex_quadrilateral import ComplexQuadrilateral from petstore_api.model.complex_quadrilateral import ComplexQuadrilateral
from petstore_api.model.composed_one_of_number_with_validations import ComposedOneOfNumberWithValidations
from petstore_api.model.danish_pig import DanishPig from petstore_api.model.danish_pig import DanishPig
from petstore_api.model.dog import Dog from petstore_api.model.dog import Dog
from petstore_api.model.dog_all_of import DogAllOf from petstore_api.model.dog_all_of import DogAllOf

View File

@@ -0,0 +1,47 @@
# coding: utf-8
"""
OpenAPI Petstore
This spec is mainly for testing Petstore server and contains fake endpoints, models. Please do not use this for any other purpose. Special characters: \" \\ # noqa: E501
The version of the OpenAPI document: 1.0.0
Generated by: https://openapi-generator.tech
"""
from __future__ import absolute_import
import sys
import unittest
import petstore_api
try:
from petstore_api.model import animal
except ImportError:
animal = sys.modules[
'petstore_api.model.animal']
from petstore_api.model.composed_one_of_number_with_validations import ComposedOneOfNumberWithValidations
class TestComposedOneOfNumberWithValidations(unittest.TestCase):
"""ComposedOneOfNumberWithValidations unit test stubs"""
def setUp(self):
pass
def tearDown(self):
pass
def testComposedOneOfNumberWithValidations(self):
"""Test ComposedOneOfNumberWithValidations"""
# we can make an instance that stores float data
inst = ComposedOneOfNumberWithValidations(10.0)
from petstore_api.model import number_with_validations
assert isinstance(inst, number_with_validations.NumberWithValidations)
# we can make an instance that stores object (dict) data
inst = ComposedOneOfNumberWithValidations(class_name="Cat", color="black")
assert isinstance(inst, ComposedOneOfNumberWithValidations)
if __name__ == '__main__':
unittest.main()

View File

@@ -118,7 +118,7 @@ class TestDrawing(unittest.TestCase):
"Required value type is {} and passed type was {} at {}") "Required value type is {} and passed type was {} at {}")
with self.assertRaisesRegexp( with self.assertRaisesRegexp(
petstore_api.ApiTypeError, petstore_api.ApiTypeError,
err_msg.format("main_shape", "Shape", "NoneType", "\['main_shape'\]") err_msg.format("main_shape", "one of \[ComplexQuadrilateral, EquilateralTriangle, IsoscelesTriangle, ScaleneTriangle, SimpleQuadrilateral\]", "NoneType", "\['main_shape'\]")
): ):
inst = Drawing( inst = Drawing(
# 'main_shape' has type 'Shape', which is a oneOf [triangle, quadrilateral] # 'main_shape' has type 'Shape', which is a oneOf [triangle, quadrilateral]

View File

@@ -142,7 +142,6 @@ class TestFakeApi(unittest.TestCase):
assert isinstance(response, array_of_enums.ArrayOfEnums) assert isinstance(response, array_of_enums.ArrayOfEnums)
assert response.value == value assert response.value == value
def test_number_with_validations(self): def test_number_with_validations(self):
"""Test case for number_with_validations """Test case for number_with_validations
@@ -173,6 +172,44 @@ class TestFakeApi(unittest.TestCase):
assert endpoint.openapi_types['body'] == (object_model_with_ref_props.ObjectModelWithRefProps,) assert endpoint.openapi_types['body'] == (object_model_with_ref_props.ObjectModelWithRefProps,)
assert endpoint.settings['response_type'] == (object_model_with_ref_props.ObjectModelWithRefProps,) assert endpoint.settings['response_type'] == (object_model_with_ref_props.ObjectModelWithRefProps,)
def test_composed_one_of_number_with_validations(self):
"""Test case for composed_one_of_number_with_validations
"""
from petstore_api.model import animal, composed_one_of_number_with_validations, number_with_validations
endpoint = self.api.composed_one_of_number_with_validations
assert endpoint.openapi_types['composed_one_of_number_with_validations_composed_one_of_number_with_validations'] == (
composed_one_of_number_with_validations.ComposedOneOfNumberWithValidations,)
assert endpoint.settings['response_type'] == (
composed_one_of_number_with_validations.ComposedOneOfNumberWithValidations,)
# serialization + deserialization works
num_with_validations = number_with_validations.NumberWithValidations(10.0)
cat_in_composed = composed_one_of_number_with_validations.ComposedOneOfNumberWithValidations(
class_name="Cat", color="black"
)
import datetime
date = datetime.date(1970, 1, 1)
body_value_simple = [
(num_with_validations, 10.0),
(cat_in_composed, {"className": "Cat", "color": "black"}),
(None, None),
(date, '1970-01-01'),
]
for (body, value_simple) in body_value_simple:
with patch.object(RESTClientObject, 'request') as mock_method:
mock_method.return_value = self.mock_response(value_simple)
response = endpoint(composed_one_of_number_with_validations_composed_one_of_number_with_validations=body)
self.assert_request_called_with(
mock_method,
'http://petstore.swagger.io:80/v2/fake/refs/composed_one_of_number_with_validations',
value_simple
)
assert isinstance(response, body.__class__)
assert response == body
def test_string(self): def test_string(self):
"""Test case for string """Test case for string

View File

@@ -32,6 +32,9 @@ class TestIntegerEnumOneValue(unittest.TestCase):
model = IntegerEnumOneValue() model = IntegerEnumOneValue()
assert model.value == 0, "With only one option, the value is assigned automatically" assert model.value == 0, "With only one option, the value is assigned automatically"
model = IntegerEnumOneValue(0)
assert model.value == 0, "We can also pass in the value as a positional arg"
model = IntegerEnumOneValue(value=0) model = IntegerEnumOneValue(value=0)
assert model.value == 0, "We can also pass in the value as a named argument" assert model.value == 0, "We can also pass in the value as a named argument"