Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion bin/openapi3/windows/python-experimental-petstore.bat
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@ If Not Exist %executable% (
)

REM set JAVA_OPTS=%JAVA_OPTS% -Xmx1024M
set ags=generate -i modules\openapi-generator\src\test\resources\3_0\petstore-with-fake-endpoints-models-for-testing.yaml -g python-experimental -o samples\openapi3\client\petstore\python-experimental --additional-properties packageName=petstore_api
set ags=generate -i modules\openapi-generator\src\test\resources\3_0\petstore-with-fake-endpoints-models-for-testing-with-http-signature.yaml -g python-experimental -o samples\openapi3\client\petstore\python-experimental --additional-properties packageName=petstore_api

java %JAVA_OPTS% -jar %executable% %ags%
Original file line number Diff line number Diff line change
Expand Up @@ -706,6 +706,56 @@ public void postProcessParameter(CodegenParameter p) {
}
}

private void addNullDefaultToOneOfAnyOfReqProps(Schema schema, CodegenModel result){
Copy link
Contributor Author

@spacether spacether Feb 16, 2020

Choose a reason for hiding this comment

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

So required variables have no default value set when we add them to our model __init__ function signatures. That means that they MUST be passed in by the user. If we have these schemas:

composedModel:
oneOf:
- modelA
- modelB
- modelC

modelA:
properties:
- propA
required:
- propA

modelB:
properties:
- propB
required:
- propB

modelC:
properties:
- propC
required:
- propC

Then we have propA, propB, and propC all as required variables in the __init__ signature of
composedModel. But our user will only pass in one of those properties because they are for oneOf schemas. Our solution here is change propA, propB, and propC to named arguments with default values of nulltype.Null

  • This lets our user pass in just one value
  • This allows the value to be None type or any other type (excluding nulltype.Null)

// for composed schema models, if the required properties are only from oneOf or anyOf models
// give them a nulltype.Null so the user can omit including them in python
ComposedSchema cs = (ComposedSchema) schema;

// these are the properties that are from properties in self cs or cs allOf
Map<String, Schema> selfProperties = new LinkedHashMap<String, Schema>();
List<String> selfRequired = new ArrayList<String>();

// these are the properties that are from properties in cs oneOf or cs anyOf
Map<String, Schema> otherProperties = new LinkedHashMap<String, Schema>();
List<String> otherRequired = new ArrayList<String>();

List<Schema> oneOfanyOfSchemas = new ArrayList<>();
List<Schema> oneOf = cs.getOneOf();
if (oneOf != null) {
oneOfanyOfSchemas.addAll(oneOf);
}
List<Schema> anyOf = cs.getAnyOf();
if (anyOf != null) {
oneOfanyOfSchemas.addAll(anyOf);
}
for (Schema sc: oneOfanyOfSchemas) {
Schema refSchema = ModelUtils.getReferencedSchema(this.openAPI, sc);
addProperties(otherProperties, otherRequired, refSchema);
}
Set<String> otherRequiredSet = new HashSet<String>(otherRequired);

List<Schema> allOf = cs.getAllOf();
if ((schema.getProperties() != null && !schema.getProperties().isEmpty()) || allOf != null) {
// NOTE: this function also adds the allOf propesrties inside schema
addProperties(selfProperties, selfRequired, schema);
}
if (result.discriminator != null) {
selfRequired.add(result.discriminator.getPropertyBaseName());
}
Set<String> selfRequiredSet = new HashSet<String>(selfRequired);

List<CodegenProperty> reqVars = result.getRequiredVars();
if (reqVars != null) {
for (CodegenProperty cp: reqVars) {
String propName = cp.baseName;
if (otherRequiredSet.contains(propName) && !selfRequiredSet.contains(propName)) {
// if var is in otherRequiredSet and is not in selfRequiredSet and is in result.requiredVars
// then set it to nullable because the user doesn't have to give a value for it
cp.setDefaultValue("nulltype.Null");
}
}
}
}

/**
* Convert OAS Model object to Codegen Model object
Expand Down Expand Up @@ -806,6 +856,11 @@ public CodegenModel fromModel(String name, Schema schema) {
if (result.imports.contains(result.classname)) {
result.imports.remove(result.classname);
}

if (result.requiredVars.size() > 0 && (result.oneOf.size() > 0 || result.anyOf.size() > 0)) {
addNullDefaultToOneOfAnyOfReqProps(schema, result);
}

return result;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import re # noqa: F401
import sys # noqa: F401

import six # noqa: F401
import nulltype # noqa: F401

from {{packageName}}.model_utils import ( # noqa: F401
ModelComposed,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,18 @@
'_from_server': _from_server,
'_configuration': _configuration,
}
model_args = {
required_args = {
{{#requiredVars}}
'{{name}}': {{name}},
{{/requiredVars}}
}
# 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)
Expand All @@ -30,9 +37,8 @@
self._additional_properties_model_instances = composed_info[2]
unused_args = composed_info[3]

{{#requiredVars}}
self.{{name}} = {{name}}
{{/requiredVars}}
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 \
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,23 @@
def __init__(self{{#requiredVars}}{{^defaultValue}}, {{name}}{{/defaultValue}}{{/requiredVars}}{{#requiredVars}}{{#defaultValue}}, {{name}}={{{defaultValue}}}{{/defaultValue}}{{/requiredVars}}, _check_type=True, _from_server=False, _path_to_item=(), _configuration=None, **kwargs): # noqa: E501
"""{{classname}} - a model defined in OpenAPI

{{#requiredVars}}{{^hasMore}} Args:{{/hasMore}}{{/requiredVars}}{{#requiredVars}}{{^defaultValue}}
{{name}} ({{{dataType}}}):{{#description}} {{description}}{{/description}}{{/defaultValue}}{{/requiredVars}}{{#requiredVars}}{{^hasMore}}
{{/hasMore}}{{/requiredVars}}
Keyword Args:{{#requiredVars}}{{#defaultValue}}
{{name}} ({{{dataType}}}):{{#description}} {{description}}.{{/description}} defaults to {{{defaultValue}}}, must be one of [{{{defaultValue}}}] # noqa: E501{{/defaultValue}}{{/requiredVars}}
{{#requiredVars}}
{{#-first}}
Args:
{{/-first}}
{{^defaultValue}}
{{name}} ({{{dataType}}}):{{#description}} {{description}}{{/description}}
{{/defaultValue}}
{{#-last}}

{{/-last}}
{{/requiredVars}}
Keyword Args:
{{#requiredVars}}
{{#defaultValue}}
{{name}} ({{{dataType}}}):{{#description}} {{description}}.{{/description}} defaults to {{{defaultValue}}}{{#allowableValues}}, must be one of [{{#enumVars}}{{{value}}}, {{/enumVars}}]{{/allowableValues}} # noqa: E501
{{/defaultValue}}
{{/requiredVars}}
_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.
Expand All @@ -18,8 +30,10 @@
_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.{{#optionalVars}}
{{name}} ({{{dataType}}}):{{#description}} {{description}}.{{/description}} [optional]{{#defaultValue}} if omitted the server will use the default value of {{{defaultValue}}}{{/defaultValue}} # noqa: E501{{/optionalVars}}
If omitted no type conversion is done.
{{#optionalVars}}
{{name}} ({{{dataType}}}):{{#description}} {{description}}.{{/description}} [optional]{{#defaultValue}} if omitted the server will use the default value of {{{defaultValue}}}{{/defaultValue}} # noqa: E501
{{/optionalVars}}
"""

self._data_store = {}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,21 +41,23 @@
if self._path_to_item:
path_to_item.extend(self._path_to_item)
path_to_item.append(name)
values = set()
if model_instances:
values = set()
for model_instance in model_instances:
if name in model_instance._data_store:
values.add(model_instance._data_store[name])
if len(values) == 1:
return list(values)[0]
len_values = len(values)
if len_values == 0:
raise ApiKeyError(
"{0} has no key '{1}'".format(type(self).__name__, name),
path_to_item
)
elif len_values == 1:
return list(values)[0]
elif len_values > 1:
raise ApiValueError(
"Values stored for property {0} in {1} difffer when looking "
"at self and self's composed instances. All values must be "
"the same".format(name, type(self).__name__),
path_to_item
)

raise ApiKeyError(
"{0} has no key '{1}'".format(type(self).__name__, name),
path_to_item
)
)
Original file line number Diff line number Diff line change
Expand Up @@ -828,7 +828,7 @@ def model_to_dict(model_instance, serialize=True):

model_instances = [model_instance]
if model_instance._composed_schemas() is not None:
model_instances = model_instance._composed_instances
model_instances.extend(model_instance._composed_instances)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

We should be extending here, this is a bug fix

for model_instance in model_instances:
for attr, value in six.iteritems(model_instance._data_store):
if serialize:
Expand Down Expand Up @@ -951,12 +951,12 @@ def get_oneof_instance(self, model_args, constant_args):
used to make instances

Returns
oneof_instance (instance)
oneof_instance (instance/None)
"""
oneof_instance = None
if len(self._composed_schemas()['oneOf']) == 0:
return oneof_instance
return None

oneof_instances = []
for oneof_class in self._composed_schemas()['oneOf']:
# transform js keys to python keys in fixed_model_args
fixed_model_args = change_keys_js_to_python(
Expand All @@ -969,20 +969,30 @@ def get_oneof_instance(self, model_args, constant_args):
if var_name in fixed_model_args:
kwargs[var_name] = fixed_model_args[var_name]

# do not try to make a model with no input args
if len(kwargs) == 0:
continue
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This fix is needed to get oneOf working


# and use it to make the instance
kwargs.update(constant_args)
try:
oneof_instance = oneof_class(**kwargs)
break
oneof_instances.append(oneof_instance)
except Exception:
pass
if oneof_instance is None:
if len(oneof_instances) == 0:
raise ApiValueError(
"Invalid inputs given to generate an instance of %s. Unable to "
"make any instances of the classes in oneOf definition." %
self.__class__.__name__
)
return oneof_instance
elif len(oneof_instances) > 1:
raise ApiValueError(
"Invalid inputs given to generate an instance of %s. Multiple "
"oneOf instances were generated when a max of one is allowed." %
self.__class__.__name__
)
return oneof_instances[0]


def get_anyof_instances(self, model_args, constant_args):
Expand Down Expand Up @@ -1012,6 +1022,10 @@ def get_anyof_instances(self, model_args, constant_args):
if var_name in fixed_model_args:
kwargs[var_name] = fixed_model_args[var_name]

# do not try to make a model with no input args
if len(kwargs) == 0:
continue
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This fix is needed to get anyOf working


# and use it to make the instance
kwargs.update(constant_args)
try:
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
nulltype
certifi >= 14.05.14
future; python_version<="2.7"
six >= 1.10
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ REQUIRES = [
"six >= 1.10",
"certifi",
"python-dateutil",
"nulltype",
{{#asyncio}}
"aiohttp >= 3.0.0",
{{/asyncio}}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1779,3 +1779,82 @@ components:
additionalProperties:
type: object
nullable: true
fruit:
properties:
color:
type: string
oneOf:
- $ref: '#/components/schemas/apple'
- $ref: '#/components/schemas/banana'
apple:
type: object
properties:
cultivar:
type: string
banana:
type: object
properties:
lengthCm:
type: number
mammal:
oneOf:
- $ref: '#/components/schemas/whale'
- $ref: '#/components/schemas/zebra'
discriminator:
propertyName: className
mapping:
whale: '#/components/schemas/whale'
zebra: '#/components/schemas/zebra'
whale:
type: object
properties:
hasBaleen:
type: boolean
hasTeeth:
type: boolean
className:
type: string
required:
- className
zebra:
type: object
properties:
type:
type: string
enum:
- plains
- mountain
- grevys
className:
type: string
required:
- className
gmFruit:
properties:
color:
type: string
anyOf:
- $ref: '#/components/schemas/apple'
- $ref: '#/components/schemas/banana'
fruitReq:
oneOf:
- $ref: '#/components/schemas/appleReq'
- $ref: '#/components/schemas/bananaReq'
appleReq:
type: object
properties:
cultivar:
type: string
mealy:
type: boolean
required:
- cultivar
bananaReq:
type: object
properties:
lengthCm:
type: number
sweet:
type: boolean
required:
- lengthCm
Loading