forked from loafle/openapi-generator-original
[python] readonly constructors (#9409)
* readonly * other tests * doc * python samples * model utils
This commit is contained in:
@@ -29,6 +29,22 @@ none_type = type(None)
|
||||
file_type = io.IOBase
|
||||
|
||||
|
||||
def convert_js_args_to_python_args(fn):
|
||||
from functools import wraps
|
||||
@wraps(fn)
|
||||
def wrapped_init(_self, *args, **kwargs):
|
||||
"""
|
||||
An attribute named `self` received from the api will conflicts with the reserved `self`
|
||||
parameter of a class method. During generation, `self` attributes are mapped
|
||||
to `_self` in models. Here, we name `_self` instead of `self` to avoid conflicts.
|
||||
"""
|
||||
spec_property_naming = kwargs.get('_spec_property_naming', False)
|
||||
if spec_property_naming:
|
||||
kwargs = change_keys_js_to_python(kwargs, _self if isinstance(_self, type) else _self.__class__)
|
||||
return fn(_self, *args, **kwargs)
|
||||
return wrapped_init
|
||||
|
||||
|
||||
class cached_property(object):
|
||||
# this caches the result of the function call for fn with no inputs
|
||||
# use this as a decorator on fuction methods that you want converted
|
||||
@@ -284,6 +300,121 @@ class OpenApiModel(object):
|
||||
return new_inst
|
||||
|
||||
|
||||
@classmethod
|
||||
@convert_js_args_to_python_args
|
||||
def _new_from_openapi_data(cls, *args, **kwargs):
|
||||
# this function uses the discriminator to
|
||||
# pick a new schema/class to instantiate because a discriminator
|
||||
# propertyName value was passed in
|
||||
|
||||
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.
|
||||
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', ())
|
||||
if (
|
||||
cls.discriminator is None or
|
||||
cls in visited_composed_classes
|
||||
):
|
||||
# Use case 1: this openapi schema (cls) does not have a discriminator
|
||||
# Use case 2: we have already visited this class before and are sure that we
|
||||
# want to instantiate it this time. We have visited this class deserializing
|
||||
# a payload with a discriminator. During that process we traveled through
|
||||
# this class but did not make an instance of it. Now we are making an
|
||||
# instance of a composed class which contains cls in it, so this time make an instance of cls.
|
||||
#
|
||||
# Here's an example of use case 2: 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 the composed schema dog Dog, we will make an instance of the
|
||||
# Animal class (because Dal has allOf: Animal) but this time we won't travel
|
||||
# through Animal's discriminator because we passed in
|
||||
# _visited_composed_classes = (Animal,)
|
||||
|
||||
return cls._from_openapi_data(*args, **kwargs)
|
||||
|
||||
# Get the name and value of the discriminator property.
|
||||
# The discriminator name is obtained from the discriminator meta-data
|
||||
# and the discriminator value is obtained from the input data.
|
||||
discr_propertyname_py = list(cls.discriminator.keys())[0]
|
||||
discr_propertyname_js = cls.attribute_map[discr_propertyname_py]
|
||||
if discr_propertyname_js in kwargs:
|
||||
discr_value = kwargs[discr_propertyname_js]
|
||||
elif discr_propertyname_py in kwargs:
|
||||
discr_value = kwargs[discr_propertyname_py]
|
||||
else:
|
||||
# The input data does not contain the discriminator property.
|
||||
path_to_item = kwargs.get('_path_to_item', ())
|
||||
raise ApiValueError(
|
||||
"Cannot deserialize input data due to missing discriminator. "
|
||||
"The discriminator property '%s' is missing at path: %s" %
|
||||
(discr_propertyname_js, path_to_item)
|
||||
)
|
||||
|
||||
# Implementation note: the last argument to get_discriminator_class
|
||||
# is a list of visited classes. get_discriminator_class may recursively
|
||||
# call itself and update the list of visited classes, and the initial
|
||||
# value must be an empty list. Hence not using 'visited_composed_classes'
|
||||
new_cls = get_discriminator_class(
|
||||
cls, discr_propertyname_py, discr_value, [])
|
||||
if new_cls is None:
|
||||
path_to_item = kwargs.get('_path_to_item', ())
|
||||
disc_prop_value = kwargs.get(
|
||||
discr_propertyname_js, kwargs.get(discr_propertyname_py))
|
||||
raise ApiValueError(
|
||||
"Cannot deserialize input data due to invalid discriminator "
|
||||
"value. The OpenAPI document has no mapping for discriminator "
|
||||
"property '%s'='%s' at path: %s" %
|
||||
(discr_propertyname_js, disc_prop_value, path_to_item)
|
||||
)
|
||||
|
||||
if new_cls in visited_composed_classes:
|
||||
# if we are making an instance of a composed schema Descendent
|
||||
# which allOf includes Ancestor, then Ancestor contains
|
||||
# a discriminator that includes Descendent.
|
||||
# So if we make an instance of Descendent, we have to make an
|
||||
# instance of Ancestor to hold the allOf properties.
|
||||
# This code detects that use case and makes the instance of Ancestor
|
||||
# For example:
|
||||
# When making an instance of Dog, _visited_composed_classes = (Dog,)
|
||||
# then we make an instance of Animal to include in dog._composed_instances
|
||||
# so when we are here, cls is Animal
|
||||
# cls.discriminator != None
|
||||
# cls not in _visited_composed_classes
|
||||
# new_cls = Dog
|
||||
# but we know we know that we already have Dog
|
||||
# because it is in visited_composed_classes
|
||||
# so make Animal here
|
||||
return cls._from_openapi_data(*args, **kwargs)
|
||||
|
||||
# Build a list containing all oneOf and anyOf descendants.
|
||||
oneof_anyof_classes = None
|
||||
if cls._composed_schemas is not None:
|
||||
oneof_anyof_classes = (
|
||||
cls._composed_schemas.get('oneOf', ()) +
|
||||
cls._composed_schemas.get('anyOf', ()))
|
||||
oneof_anyof_child = new_cls in oneof_anyof_classes
|
||||
kwargs['_visited_composed_classes'] = visited_composed_classes + (cls,)
|
||||
|
||||
if cls._composed_schemas.get('allOf') and oneof_anyof_child:
|
||||
# Validate that we can make self because when we make the
|
||||
# new_cls it will not include the allOf validations in self
|
||||
self_inst = cls._from_openapi_data(*args, **kwargs)
|
||||
|
||||
|
||||
new_inst = new_cls._new_from_openapi_data(*args, **kwargs)
|
||||
return new_inst
|
||||
|
||||
|
||||
class ModelSimple(OpenApiModel):
|
||||
"""the parent class of models whose type != object in their
|
||||
swagger/openapi"""
|
||||
@@ -1208,14 +1339,14 @@ def deserialize_model(model_data, model_class, path_to_item, check_type,
|
||||
_spec_property_naming=spec_property_naming)
|
||||
|
||||
if issubclass(model_class, ModelSimple):
|
||||
return model_class(model_data, **kw_args)
|
||||
return model_class._new_from_openapi_data(model_data, **kw_args)
|
||||
elif isinstance(model_data, list):
|
||||
return model_class(*model_data, **kw_args)
|
||||
return model_class._new_from_openapi_data(*model_data, **kw_args)
|
||||
if isinstance(model_data, dict):
|
||||
kw_args.update(model_data)
|
||||
return model_class(**kw_args)
|
||||
return model_class._new_from_openapi_data(**kw_args)
|
||||
elif isinstance(model_data, PRIMITIVE_TYPES):
|
||||
return model_class(model_data, **kw_args)
|
||||
return model_class._new_from_openapi_data(model_data, **kw_args)
|
||||
|
||||
|
||||
def deserialize_file(response_data, configuration, content_disposition=None):
|
||||
@@ -1577,22 +1708,6 @@ def get_valid_classes_phrase(input_classes):
|
||||
return "is one of [{0}]".format(", ".join(all_class_names))
|
||||
|
||||
|
||||
def convert_js_args_to_python_args(fn):
|
||||
from functools import wraps
|
||||
@wraps(fn)
|
||||
def wrapped_init(_self, *args, **kwargs):
|
||||
"""
|
||||
An attribute named `self` received from the api will conflicts with the reserved `self`
|
||||
parameter of a class method. During generation, `self` attributes are mapped
|
||||
to `_self` in models. Here, we name `_self` instead of `self` to avoid conflicts.
|
||||
"""
|
||||
spec_property_naming = kwargs.get('_spec_property_naming', False)
|
||||
if spec_property_naming:
|
||||
kwargs = change_keys_js_to_python(kwargs, _self.__class__)
|
||||
return fn(_self, *args, **kwargs)
|
||||
return wrapped_init
|
||||
|
||||
|
||||
def get_allof_instances(self, model_args, constant_args):
|
||||
"""
|
||||
Args:
|
||||
|
||||
Reference in New Issue
Block a user