Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
5bc9540
Adds additionalproperties feature in python
spacether Feb 4, 2019
64d5444
Adds ensure-up-to-date updates
spacether Feb 4, 2019
ec73f56
Adds docstring description of the model argument inputs
spacether Feb 4, 2019
5cdfe51
Adds ensure up to date oepnapi3 python updates
spacether Feb 4, 2019
0a52027
Adds test fix
spacether Feb 4, 2019
8be21b0
Adds fix for Shippable, gives the additionalProperties CodegenParamet…
spacether Feb 4, 2019
ba7ea49
Adds function to set freeform types to string of all types
spacether Feb 7, 2019
88f8197
Adds postProcessAllModels function which processes and fixes dataType…
spacether Feb 7, 2019
0590d05
Adds models with additionalproperties of each type and model tests
spacether Feb 9, 2019
372ae60
Adds _check_type parameter to model, adds additionalproperties tests
spacether Feb 11, 2019
5b4f108
Creates utils module, adds additionalproperties map for serialization
spacether Feb 12, 2019
2e33cf1
Removes additional_properties_map, creates model_to_dict function to …
spacether Feb 13, 2019
983f570
Improves docstring for model_to_dict
spacether Feb 14, 2019
f218ed5
Adds class type definition in models
spacether Feb 17, 2019
7656756
Adds datetime and date type classes, adds general OpenApiException, r…
spacether Feb 18, 2019
7ac9285
Adds class imports to models, python generator uses dataType for docs…
spacether Feb 20, 2019
67dbb9c
Model discriminator now stores classes, adds __deserialize_model in a…
spacether Feb 22, 2019
9680dfd
Creates exceptions module, all deserialization tests in py2 except 1 …
spacether Feb 23, 2019
05fe1aa
Adds validate_and_convert_types, python 2.0 tests pass except for tes…
spacether Feb 25, 2019
e8f97fa
Adds anytype deserialization tests in python swagger client
spacether Feb 28, 2019
59b2d4c
Fixes typos
spacether Feb 28, 2019
e158601
Adds file deserialization test
spacether Mar 2, 2019
7d84ef2
Updates all v2 and v3 python samples
spacether Mar 3, 2019
b2446cf
Removes dubug flag, reverts readme, in python generator tweaks suppor…
spacether Mar 3, 2019
9e9b94e
Adds dict instantiationType back in
spacether Mar 3, 2019
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
Prev Previous commit
Next Next commit
Adds validate_and_convert_types, python 2.0 tests pass except for tes…
…t_enum_test
  • Loading branch information
spacether committed Mar 2, 2019
commit 05fe1aa8b81688fc1e202288404cf1741d70649c
13 changes: 8 additions & 5 deletions modules/openapi-generator/src/main/resources/python/api.mustache
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,16 @@ import re # noqa: F401
import six

from {{packageName}}.api_client import ApiClient
from {{packageName}}.utils import ( # noqa: F401
from {{packageName}}.exceptions import (
ApiTypeError,
ApiValueError,
ApiValueError
)
from {{packageName}}.model_utils import ( # noqa: F401
date,
datetime,
file_type,
none_type,
validate_type
validate_and_convert_types
)
{{#imports}}
{{{import}}}
Expand Down Expand Up @@ -131,9 +133,10 @@ class {{classname}}(object):

if _check_type:
for param_name, param_value in six.iteritems(local_var_params):
required_type = data_types_by_param.get(param_name)
required_types_mixed = data_types_by_param.get(param_name)
if required_type:
validate_type(param_value, required_type, [param_name])
local_var_params[param_name] = validate_and_convert_types(
param_value, required_types_mixed, [param_name])
{{/hasParams}}
{{#allParams}}
{{^isNullable}}
Expand Down
212 changes: 16 additions & 196 deletions modules/openapi-generator/src/main/resources/python/api_client.mustache
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import os
import re
import tempfile

from dateutil.parser import parse
# python 2 and python 3 compatibility library
import six
from six.moves.urllib.parse import quote
Expand All @@ -22,21 +21,19 @@ import tornado.gen

from {{packageName}}.configuration import Configuration
from {{packageName}}.exceptions import (
ApiKeyError,
ApiTypeError,
ApiValueError,
ApiValueError
)
from {{packageName}}.model_utils import (
OpenApiModel,
change_keys_js_to_python,
date,
datetime,
deserialize_file,
file_type,
get_parent_key_or_index,
model_to_dict,
none_type,
order_response_types,
remove_uncoercible,
validate_type
validate_and_convert_types
)
import {{modelPackage}}
from {{packageName}} import rest
Expand Down Expand Up @@ -255,78 +252,26 @@ class ApiClient(object):
# save response body into a tmp file and return the instance
if response_types_mixed == [file_type]:
content_disposition = response.getheader("Content-Disposition")
return self.__deserialize_file(response.data,
return deserialize_file(response.data, self.configuration,
content_disposition=content_disposition)

# fetch data from response object
try:
data = json.loads(response.data)
received_data = json.loads(response.data)
except ValueError:
data = response.data
# this path is used if we are deserializing string data
received_data = response.data

return self.__deserialize(data, response_types_mixed)

def __deserialize(self, data, response_types_mixed):
"""Deserializes dict, list, str into an object.

:param data: dict, list or str.
:param response_types_mixed: For the response, a list of
valid classes, or a list tuples of valid classes, or a dict where
the value is a tuple of value classes.

:return: object.
"""
# we put our data in a dict so all values can be accessible by a key
# or index, so we can use parent[key_or_index] = value
# to update our input data
input_data = {'received_data': copy.deepcopy(data)}
deserializing = True
validated = False
while deserializing:
try:
validate_type(
input_data['received_data'],
response_types_mixed,
['received_data']
)
validated = True
deserializing = False
except ApiTypeError as exc:
valid_classes = exc.required_types
valid_classes_ordered = order_response_types(valid_classes)
valid_classes_coercible = remove_uncoercible(
valid_classes_ordered, exc.current_item)
if not valid_classes_coercible or exc.key_type:
# we do not handle keytype errors, json will take care
# of this for us
deserializing = False
continue
deserialized_item = False
parent, key_or_index = get_parent_key_or_index(
input_data, exc.path_to_item)
for valid_class in valid_classes_coercible:
if issubclass(valid_class, OpenApiModel):
deserialized_item = self.__deserialize_model(
parent[key_or_index], valid_class)
elif isinstance(valid_class, file_type):
deserialized_item = self.__deserialize_file(
parent[key_or_index])
else:
deserialized_item = self.__deserialize_primitive(
parent[key_or_index], valid_class)
if deserialized_item:
parent[key_or_index] = deserialized_item
break
if not deserialized_item:
deserializing = False
if validated:
return input_data['received_data']
# we were unable to deserialize the results, raise an exception
validate_type(
input_data['received_data'],
# start our path as 'received_data' so users have some context
# if they are deserializing a string and the data type is wrong
deserialized_data = validate_and_convert_types(
received_data,
response_types_mixed,
['received_data']
['received_data'],
configuration=self.configuration
)
return deserialized_data


def call_api(self, resource_path, method,
path_params=None, query_params=None, header_params=None,
Expand Down Expand Up @@ -572,128 +517,3 @@ class ApiClient(object):
raise ApiValueError(
'Authentication token must be in `query` or `header`'
)

def __deserialize_file(self, response_data, content_disposition=None):
"""Deserializes body to file

Saves response body into a file in a temporary folder,
using the filename from the `Content-Disposition` header if provided.

:param response_data: the file data to write
:param content_disposition: the value of the Content-Disposition
header
:return: file path.
"""
fd, path = tempfile.mkstemp(dir=self.configuration.temp_folder_path)
os.close(fd)
os.remove(path)

if content_disposition:
filename = re.search(r'filename=[\'"]?([^\'"\s]+)[\'"]?',
content_disposition).group(1)
path = os.path.join(os.path.dirname(path), filename)

with open(path, "wb") as f:
f.write(response_data)

return path

def __deserialize_primitive(self, data, klass):
"""Deserializes string to primitive type.

:param data: str/int/float
:param klass: str/class the class to convert to

:return: int, float, str, bool, date, datetime
"""
additional_message = ""
try:
if klass in {datetime, date}:
additional_message = (
". If you need your parameter to have a fallback "
"string value, please set its type as `type: {}` in your "
"spec. That allows the value to be any type."
)
if klass == datetime:
# The string should be in iso8601 datetime format.
return parse(data)
elif klass == date:
return parse(data).date()
else:
return klass(data)
except (OverflowError, ValueError):
# parse can raise OverflowError
raise ApiValueError(
"Failed to parse {0} as {1}{2}".format(
data, klass, additional_message
)
)

def __get_model_instance(self, model_data, model_class):
if isinstance(model_data, list):
instance = model_class(*model_data, _check_type=True)
elif isinstance(model_data, dict):
instance = model_class(**model_data, _check_type=True)

if hasattr(instance, 'get_real_child_model'):
discriminator_class = instance.get_real_child_model(model_data)
if discriminator_class:
if isinstance(model_data, list):
instance = discriminator_class(*model_data,
_check_type=True)
elif isinstance(model_data, dict):
instance = discriminator_class(**model_data,
_check_type=True)
return instance

def __deserialize_model(self, model_data, model_class):
"""Deserializes model_data to model instance.

:param model_data: list or dict
:param model_class: model class
:return: model instance
"""
fixed_model_data = copy.deepcopy(model_data)
if isinstance(fixed_model_data, dict):
fixed_model_data = change_keys_js_to_python(fixed_model_data,
model_class)

deserializing = True
instance = None
while deserializing:
try:
instance = self.__get_model_instance(fixed_model_data,
model_class)
deserializing = False
except ApiTypeError as exc:
valid_classes = exc.required_types
valid_classes_ordered = order_response_types(valid_classes)
valid_classes_coercible = remove_uncoercible(
valid_classes_ordered, exc.current_item)
if not valid_classes_coercible or exc.key_type:
# we do not handle keytype errors, json will take care
# of this for us
deserializing = False
continue
deserialized_item = False
parent, key_or_index = get_parent_key_or_index(
fixed_model_data, exc.path_to_item)
for valid_class in valid_classes_coercible:
if issubclass(valid_class, OpenApiModel):
deserialized_item = self.__deserialize_model(
parent[key_or_index], valid_class)
elif isinstance(valid_class, file_type):
deserialized_item = self.__deserialize_file(
parent[key_or_index])
else:
deserialized_item = self.__deserialize_primitive(
parent[key_or_index], valid_class)
if deserialized_item:
parent[key_or_index] = deserialized_item
break
if not deserialized_item:
deserializing = False
if not instance:
# raise the exception
instance = self.__get_model_instance(fixed_model_data, model_class)
return instance
Original file line number Diff line number Diff line change
Expand Up @@ -8,55 +8,67 @@ class OpenApiException(Exception):


class ApiTypeError(OpenApiException, TypeError):
def __init__(self, required_types, current_item, path_to_item,
key_type=False):
def __init__(self, msg, path_to_item=None, valid_classes=None,
key_type=None):
""" Raises an exception for TypeErrors

Args:
required_types (tuple): the primitive classes that current item
should be an instance of
current_item (any): the value which is the incorrect type
path_to_item (list): a list of keys an indices to get to the
current_item
msg (str): the exception message

Keyword Args:
path_to_item (list): a list of keys an indices to get to the
current_item
None if unset
valid_classes (tuple): the primitive classes that current item
should be an instance of
None if unset
key_type (bool): False if our value is a value in a dict
True if it is a key in a dict
False if our item is an item in a list
None if unset
"""
key_or_value = 'value'
if key_type:
key_or_value = 'key'
msg = (
"Invalid type for variable {0}. Required {1} type is {2} and "
"passed type was {3} at location={4}".format(
path_to_item[0],
key_or_value,
required_types,
type(current_item),
path_to_item
)
)
super(ApiTypeError, self).__init__(msg)
self.key_type = key_type
self.path_to_item = path_to_item
self.current_item = current_item
self.required_types = required_types
self.valid_classes = valid_classes
self.key_type = key_type
full_msg = msg
if path_to_item:
full_msg = "{0} at {1}".format(msg, render_path(path_to_item))
super(ApiTypeError, self).__init__(full_msg)


class ApiValueError(OpenApiException, ValueError):
def __init__(self, msg):
super(ApiTypeError, self).__init__(msg)
def __init__(self, msg, path_to_item=None):
"""
Args:
msg (str): the exception message

Keyword Args:
path_to_item (list) the path to the exception in the
received_data dict. None if unset
"""

self.path_to_item = path_to_item
full_msg = msg
if path_to_item:
full_msg = "{0} at {1}".format(msg, render_path(path_to_item))
super(ApiValueError, self).__init__(full_msg)


class ApiKeyError(OpenApiException, KeyError):
def __init__(self, msg, path_to_item):
def __init__(self, msg, path_to_item=None):
"""
Args:
path_to_item (list): a list of keys an indices to get to the
current_item
msg (str): the exception message

super(ApiKeyError, self).__init__(msg)
Keyword Args:
path_to_item (None/list) the path to the exception in the
received_data dict
"""
self.path_to_item = path_to_item
full_msg = msg
if path_to_item:
full_msg = "{0} at {1}".format(msg, render_path(path_to_item))
super(ApiKeyError, self).__init__(full_msg)


class ApiException(OpenApiException):
Expand Down Expand Up @@ -85,3 +97,8 @@ class ApiException(OpenApiException):
error_message += "HTTP response body: {0}\n".format(self.body)

return error_message


def render_path(path_to_item):
"""Returns a string representation of a path"""
return "".join(["[{0}]".format(repr(pth)) for pth in path_to_item])
Loading