[codegen][python-experimental] Add configuration knob to disable JSON schema validation (#6227)

* Add knob to disable JSON schema structural validation

* Add knob to disable JSON schema structural validation

* Fix formatting issues

* execute sample scripts

* execute sample scripts

* fix multipleOf validation issue

* Add validation log for multipleOf. Add customizable validation checks. add unit tests for JSON schema validation

* Add validation log for multipleOf. Add customizable validation checks. add unit tests for JSON schema validation

* Add validation log for multipleOf. Add customizable validation checks. add unit tests for JSON schema validation

* Add validation log for multipleOf. Add customizable validation checks. add unit tests for JSON schema validation. Fix for python 2

* address review comments
This commit is contained in:
Sebastien Rosset 2020-05-17 09:11:01 -07:00 committed by GitHub
parent 04ff319502
commit b4954b0d80
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
30 changed files with 548 additions and 60 deletions

View File

@ -2956,10 +2956,13 @@ public class DefaultCodegen implements CodegenConfig {
if (p.getExclusiveMaximum() != null) {
property.exclusiveMaximum = p.getExclusiveMaximum();
}
if (p.getMultipleOf() != null) {
property.multipleOf = p.getMultipleOf();
}
// check if any validation rule defined
// exclusive* are noop without corresponding min/max
if (property.minimum != null || property.maximum != null)
if (property.minimum != null || property.maximum != null || p.getMultipleOf() != null)
property.hasValidation = true;
} else if (ModelUtils.isBooleanSchema(p)) { // boolean type
@ -3031,8 +3034,9 @@ public class DefaultCodegen implements CodegenConfig {
// check if any validation rule defined
// exclusive* are noop without corresponding min/max
if (property.minimum != null || property.maximum != null)
if (property.minimum != null || property.maximum != null || p.getMultipleOf() != null) {
property.hasValidation = true;
}
} else if (ModelUtils.isFreeFormObject(p)) {
property.isFreeFormObject = true;
@ -4151,7 +4155,7 @@ public class DefaultCodegen implements CodegenConfig {
if (codegenParameter.maximum != null || codegenParameter.minimum != null ||
codegenParameter.maxLength != null || codegenParameter.minLength != null ||
codegenParameter.maxItems != null || codegenParameter.minItems != null ||
codegenParameter.pattern != null) {
codegenParameter.pattern != null || codegenParameter.multipleOf != null) {
codegenParameter.hasValidation = true;
}
@ -5629,7 +5633,7 @@ public class DefaultCodegen implements CodegenConfig {
if (codegenParameter.maximum != null || codegenParameter.minimum != null ||
codegenParameter.maxLength != null || codegenParameter.minLength != null ||
codegenParameter.maxItems != null || codegenParameter.minItems != null ||
codegenParameter.pattern != null) {
codegenParameter.pattern != null || codegenParameter.multipleOf != null) {
codegenParameter.hasValidation = true;
}

View File

@ -14,8 +14,15 @@ import urllib3
import six
from six.moves import http_client as httplib
from {{packageName}}.exceptions import ApiValueError
JSON_SCHEMA_VALIDATION_KEYWORDS = {
'multipleOf', 'maximum', 'exclusiveMaximum',
'minimum', 'exclusiveMinimum', 'maxLength',
'minLength', 'pattern', 'maxItems', 'minItems'
}
class Configuration(object):
"""NOTE: This class is auto generated by OpenAPI Generator
@ -43,6 +50,19 @@ class Configuration(object):
then all undeclared properties received by the server are injected into the
additional properties map. In that case, there are undeclared properties, and
nothing to discard.
:param disabled_client_side_validations (string): Comma-separated list of
JSON schema validation keywords to disable JSON schema structural validation
rules. The following keywords may be specified: multipleOf, maximum,
exclusiveMaximum, minimum, exclusiveMinimum, maxLength, minLength, pattern,
maxItems, minItems.
By default, the validation is performed for data generated locally by the client
and data received from the server, independent of any validation performed by
the server side. If the input data does not satisfy the JSON schema validation
rules specified in the OpenAPI document, an exception is raised.
If disabled_client_side_validations is set, structural validation is
disabled. This can be useful to troubleshoot data validation problem, such as
when the OpenAPI document validation rules do not match the actual API data
received by the server.
{{#hasHttpSignatureMethods}}
:param signing_info: Configuration parameters for the HTTP signature security scheme.
Must be an instance of {{{packageName}}}.signing.HttpSigningConfiguration
@ -139,6 +159,7 @@ conf = {{{packageName}}}.Configuration(
api_key=None, api_key_prefix=None,
username=None, password=None,
discard_unknown_keys=False,
disabled_client_side_validations="",
{{#hasHttpSignatureMethods}}
signing_info=None,
{{/hasHttpSignatureMethods}}
@ -172,6 +193,7 @@ conf = {{{packageName}}}.Configuration(
"""Password for HTTP basic authentication
"""
self.discard_unknown_keys = discard_unknown_keys
self.disabled_client_side_validations = disabled_client_side_validations
{{#hasHttpSignatureMethods}}
if signing_info is not None:
signing_info.host = host
@ -277,6 +299,13 @@ conf = {{{packageName}}}.Configuration(
def __setattr__(self, name, value):
object.__setattr__(self, name, value)
if name == 'disabled_client_side_validations':
s = set(filter(None, value.split(',')))
for v in s:
if v not in JSON_SCHEMA_VALIDATION_KEYWORDS:
raise ApiValueError(
"Invalid keyword: '{0}''".format(v))
self._disabled_client_side_validations = s
{{#hasHttpSignatureMethods}}
if name == "signing_info" and value is not None:
# Ensure the host paramater from signing info is the same as

View File

@ -376,7 +376,8 @@ class Endpoint(object):
check_validations(
self.validations,
(param,),
kwargs[param]
kwargs[param],
configuration=self.api_client.configuration
)
if kwargs['_check_input_type'] is False:

View File

@ -57,6 +57,9 @@
{{#-first}}'flags': (re.{{.}}{{/-first}}{{^-first}} re.{{.}}{{/-first}}{{^-last}} | {{/-last}}{{#-last}}){{/-last}}{{/vendorExtensions.x-modifiers}}
},
{{/pattern}}
{{#multipleOf}}
'multiple_of': {{multipleOf}},
{{/multipleOf}}
},
{{/hasValidation}}
{{/requiredVars}}
@ -87,6 +90,9 @@
{{#-first}}'flags': (re.{{.}}{{/-first}}{{^-first}} re.{{.}}{{/-first}}{{^-last}} | {{/-last}}{{#-last}}){{/-last}}{{/vendorExtensions.x-modifiers}}
},
{{/pattern}}
{{#multipleOf}}
'multiple_of': {{multipleOf}},
{{/multipleOf}}
},
{{/hasValidation}}
{{/optionalVars}}

View File

@ -45,6 +45,7 @@
check_validations(
self.validations,
(name,),
value
value,
self._configuration
)
self.__dict__['_data_store'][name] = value

View File

@ -369,17 +369,50 @@ def check_allowed_values(allowed_values, input_variable_path, input_values):
)
def check_validations(validations, input_variable_path, input_values):
def is_json_validation_enabled(schema_keyword, configuration=None):
"""Returns true if JSON schema validation is enabled for the specified
validation keyword. This can be used to skip JSON schema structural validation
as requested in the configuration.
Args:
schema_keyword (string): the name of a JSON schema validation keyword.
configuration (Configuration): the configuration class.
"""
return (configuration is None or
not hasattr(configuration, '_disabled_client_side_validations') or
schema_keyword not in configuration._disabled_client_side_validations)
def check_validations(
validations, input_variable_path, input_values,
configuration=None):
"""Raises an exception if the input_values are invalid
Args:
validations (dict): the validation dictionary
input_variable_path (tuple): the path to the input variable
validations (dict): the validation dictionary.
input_variable_path (tuple): the path to the input variable.
input_values (list/str/int/float/date/datetime): the values that we
are checking
are checking.
configuration (Configuration): the configuration class.
"""
current_validations = validations[input_variable_path]
if ('max_length' in current_validations and
if (is_json_validation_enabled('multipleOf', configuration) and
'multiple_of' in current_validations and
isinstance(input_values, (int, float)) and
not (float(input_values) / current_validations['multiple_of']).is_integer()):
# Note 'multipleOf' will be as good as the floating point arithmetic.
raise ApiValueError(
"Invalid value for `%s`, value must be a multiple of "
"`%s`" % (
input_variable_path[0],
current_validations['multiple_of']
)
)
if (is_json_validation_enabled('maxLength', configuration) and
'max_length' in current_validations and
len(input_values) > current_validations['max_length']):
raise ApiValueError(
"Invalid value for `%s`, length must be less than or equal to "
@ -389,7 +422,8 @@ def check_validations(validations, input_variable_path, input_values):
)
)
if ('min_length' in current_validations and
if (is_json_validation_enabled('minLength', configuration) and
'min_length' in current_validations and
len(input_values) < current_validations['min_length']):
raise ApiValueError(
"Invalid value for `%s`, length must be greater than or equal to "
@ -399,7 +433,8 @@ def check_validations(validations, input_variable_path, input_values):
)
)
if ('max_items' in current_validations and
if (is_json_validation_enabled('maxItems', configuration) and
'max_items' in current_validations and
len(input_values) > current_validations['max_items']):
raise ApiValueError(
"Invalid value for `%s`, number of items must be less than or "
@ -409,7 +444,8 @@ def check_validations(validations, input_variable_path, input_values):
)
)
if ('min_items' in current_validations and
if (is_json_validation_enabled('minItems', configuration) and
'min_items' in current_validations and
len(input_values) < current_validations['min_items']):
raise ValueError(
"Invalid value for `%s`, number of items must be greater than or "
@ -432,7 +468,8 @@ def check_validations(validations, input_variable_path, input_values):
max_val = input_values
min_val = input_values
if ('exclusive_maximum' in current_validations and
if (is_json_validation_enabled('exclusiveMaximum', configuration) and
'exclusive_maximum' in current_validations and
max_val >= current_validations['exclusive_maximum']):
raise ApiValueError(
"Invalid value for `%s`, must be a value less than `%s`" % (
@ -441,7 +478,8 @@ def check_validations(validations, input_variable_path, input_values):
)
)
if ('inclusive_maximum' in current_validations and
if (is_json_validation_enabled('maximum', configuration) and
'inclusive_maximum' in current_validations and
max_val > current_validations['inclusive_maximum']):
raise ApiValueError(
"Invalid value for `%s`, must be a value less than or equal to "
@ -451,7 +489,8 @@ def check_validations(validations, input_variable_path, input_values):
)
)
if ('exclusive_minimum' in current_validations and
if (is_json_validation_enabled('exclusiveMinimum', configuration) and
'exclusive_minimum' in current_validations and
min_val <= current_validations['exclusive_minimum']):
raise ApiValueError(
"Invalid value for `%s`, must be a value greater than `%s`" %
@ -461,7 +500,8 @@ def check_validations(validations, input_variable_path, input_values):
)
)
if ('inclusive_minimum' in current_validations and
if (is_json_validation_enabled('minimum', configuration) and
'inclusive_minimum' in current_validations and
min_val < current_validations['inclusive_minimum']):
raise ApiValueError(
"Invalid value for `%s`, must be a value greater than or equal "
@ -471,7 +511,8 @@ def check_validations(validations, input_variable_path, input_values):
)
)
flags = current_validations.get('regex', {}).get('flags', 0)
if ('regex' in current_validations and
if (is_json_validation_enabled('pattern', configuration) and
'regex' in current_validations and
not re.search(current_validations['regex']['pattern'],
input_values, flags=flags)):
err_msg = r"Invalid value for `%s`, must match regular expression `%s`" % (

View File

@ -1426,6 +1426,7 @@ components:
type: integer
maximum: 100
minimum: 10
multipleOf: 2
int32:
type: integer
format: int32
@ -1438,6 +1439,7 @@ components:
maximum: 543.2
minimum: 32.1
type: number
multipleOf: 32.5
float:
type: number
format: float

View File

@ -19,8 +19,15 @@ import urllib3
import six
from six.moves import http_client as httplib
from petstore_api.exceptions import ApiValueError
JSON_SCHEMA_VALIDATION_KEYWORDS = {
'multipleOf', 'maximum', 'exclusiveMaximum',
'minimum', 'exclusiveMinimum', 'maxLength',
'minLength', 'pattern', 'maxItems', 'minItems'
}
class Configuration(object):
"""NOTE: This class is auto generated by OpenAPI Generator
@ -48,6 +55,19 @@ class Configuration(object):
then all undeclared properties received by the server are injected into the
additional properties map. In that case, there are undeclared properties, and
nothing to discard.
:param disabled_client_side_validations (string): Comma-separated list of
JSON schema validation keywords to disable JSON schema structural validation
rules. The following keywords may be specified: multipleOf, maximum,
exclusiveMaximum, minimum, exclusiveMinimum, maxLength, minLength, pattern,
maxItems, minItems.
By default, the validation is performed for data generated locally by the client
and data received from the server, independent of any validation performed by
the server side. If the input data does not satisfy the JSON schema validation
rules specified in the OpenAPI document, an exception is raised.
If disabled_client_side_validations is set, structural validation is
disabled. This can be useful to troubleshoot data validation problem, such as
when the OpenAPI document validation rules do not match the actual API data
received by the server.
:Example:
@ -93,6 +113,7 @@ conf = petstore_api.Configuration(
api_key=None, api_key_prefix=None,
username=None, password=None,
discard_unknown_keys=False,
disabled_client_side_validations="",
):
"""Constructor
"""
@ -123,6 +144,7 @@ conf = petstore_api.Configuration(
"""Password for HTTP basic authentication
"""
self.discard_unknown_keys = discard_unknown_keys
self.disabled_client_side_validations = disabled_client_side_validations
self.access_token = None
"""access token for OAuth/Bearer
"""
@ -201,6 +223,13 @@ conf = petstore_api.Configuration(
def __setattr__(self, name, value):
object.__setattr__(self, name, value)
if name == 'disabled_client_side_validations':
s = set(filter(None, value.split(',')))
for v in s:
if v not in JSON_SCHEMA_VALIDATION_KEYWORDS:
raise ApiValueError(
"Invalid keyword: '{0}''".format(v))
self._disabled_client_side_validations = s
@classmethod
def set_default(cls, default):

View File

@ -249,7 +249,8 @@ class Endpoint(object):
check_validations(
self.validations,
(param,),
kwargs[param]
kwargs[param],
configuration=self.api_client.configuration
)
if kwargs['_check_input_type'] is False:

View File

@ -2192,7 +2192,8 @@ class Endpoint(object):
check_validations(
self.validations,
(param,),
kwargs[param]
kwargs[param],
configuration=self.api_client.configuration
)
if kwargs['_check_input_type'] is False:

View File

@ -251,7 +251,8 @@ class Endpoint(object):
check_validations(
self.validations,
(param,),
kwargs[param]
kwargs[param],
configuration=self.api_client.configuration
)
if kwargs['_check_input_type'] is False:

View File

@ -1256,7 +1256,8 @@ class Endpoint(object):
check_validations(
self.validations,
(param,),
kwargs[param]
kwargs[param],
configuration=self.api_client.configuration
)
if kwargs['_check_input_type'] is False:

View File

@ -590,7 +590,8 @@ class Endpoint(object):
check_validations(
self.validations,
(param,),
kwargs[param]
kwargs[param],
configuration=self.api_client.configuration
)
if kwargs['_check_input_type'] is False:

View File

@ -1049,7 +1049,8 @@ class Endpoint(object):
check_validations(
self.validations,
(param,),
kwargs[param]
kwargs[param],
configuration=self.api_client.configuration
)
if kwargs['_check_input_type'] is False:

View File

@ -20,8 +20,15 @@ import urllib3
import six
from six.moves import http_client as httplib
from petstore_api.exceptions import ApiValueError
JSON_SCHEMA_VALIDATION_KEYWORDS = {
'multipleOf', 'maximum', 'exclusiveMaximum',
'minimum', 'exclusiveMinimum', 'maxLength',
'minLength', 'pattern', 'maxItems', 'minItems'
}
class Configuration(object):
"""NOTE: This class is auto generated by OpenAPI Generator
@ -49,6 +56,19 @@ class Configuration(object):
then all undeclared properties received by the server are injected into the
additional properties map. In that case, there are undeclared properties, and
nothing to discard.
:param disabled_client_side_validations (string): Comma-separated list of
JSON schema validation keywords to disable JSON schema structural validation
rules. The following keywords may be specified: multipleOf, maximum,
exclusiveMaximum, minimum, exclusiveMinimum, maxLength, minLength, pattern,
maxItems, minItems.
By default, the validation is performed for data generated locally by the client
and data received from the server, independent of any validation performed by
the server side. If the input data does not satisfy the JSON schema validation
rules specified in the OpenAPI document, an exception is raised.
If disabled_client_side_validations is set, structural validation is
disabled. This can be useful to troubleshoot data validation problem, such as
when the OpenAPI document validation rules do not match the actual API data
received by the server.
:Example:
@ -94,6 +114,7 @@ conf = petstore_api.Configuration(
api_key=None, api_key_prefix=None,
username=None, password=None,
discard_unknown_keys=False,
disabled_client_side_validations="",
):
"""Constructor
"""
@ -124,6 +145,7 @@ conf = petstore_api.Configuration(
"""Password for HTTP basic authentication
"""
self.discard_unknown_keys = discard_unknown_keys
self.disabled_client_side_validations = disabled_client_side_validations
self.access_token = None
"""access token for OAuth/Bearer
"""
@ -205,6 +227,13 @@ conf = petstore_api.Configuration(
def __setattr__(self, name, value):
object.__setattr__(self, name, value)
if name == 'disabled_client_side_validations':
s = set(filter(None, value.split(',')))
for v in s:
if v not in JSON_SCHEMA_VALIDATION_KEYWORDS:
raise ApiValueError(
"Invalid keyword: '{0}''".format(v))
self._disabled_client_side_validations = s
@classmethod
def set_default(cls, default):

View File

@ -112,7 +112,8 @@ class OpenApiModel(object):
check_validations(
self.validations,
(name,),
value
value,
self._configuration
)
self.__dict__['_data_store'][name] = value
@ -635,17 +636,50 @@ def check_allowed_values(allowed_values, input_variable_path, input_values):
)
def check_validations(validations, input_variable_path, input_values):
def is_json_validation_enabled(schema_keyword, configuration=None):
"""Returns true if JSON schema validation is enabled for the specified
validation keyword. This can be used to skip JSON schema structural validation
as requested in the configuration.
Args:
schema_keyword (string): the name of a JSON schema validation keyword.
configuration (Configuration): the configuration class.
"""
return (configuration is None or
not hasattr(configuration, '_disabled_client_side_validations') or
schema_keyword not in configuration._disabled_client_side_validations)
def check_validations(
validations, input_variable_path, input_values,
configuration=None):
"""Raises an exception if the input_values are invalid
Args:
validations (dict): the validation dictionary
input_variable_path (tuple): the path to the input variable
validations (dict): the validation dictionary.
input_variable_path (tuple): the path to the input variable.
input_values (list/str/int/float/date/datetime): the values that we
are checking
are checking.
configuration (Configuration): the configuration class.
"""
current_validations = validations[input_variable_path]
if ('max_length' in current_validations and
if (is_json_validation_enabled('multipleOf', configuration) and
'multiple_of' in current_validations and
isinstance(input_values, (int, float)) and
not (float(input_values) / current_validations['multiple_of']).is_integer()):
# Note 'multipleOf' will be as good as the floating point arithmetic.
raise ApiValueError(
"Invalid value for `%s`, value must be a multiple of "
"`%s`" % (
input_variable_path[0],
current_validations['multiple_of']
)
)
if (is_json_validation_enabled('maxLength', configuration) and
'max_length' in current_validations and
len(input_values) > current_validations['max_length']):
raise ApiValueError(
"Invalid value for `%s`, length must be less than or equal to "
@ -655,7 +689,8 @@ def check_validations(validations, input_variable_path, input_values):
)
)
if ('min_length' in current_validations and
if (is_json_validation_enabled('minLength', configuration) and
'min_length' in current_validations and
len(input_values) < current_validations['min_length']):
raise ApiValueError(
"Invalid value for `%s`, length must be greater than or equal to "
@ -665,7 +700,8 @@ def check_validations(validations, input_variable_path, input_values):
)
)
if ('max_items' in current_validations and
if (is_json_validation_enabled('maxItems', configuration) and
'max_items' in current_validations and
len(input_values) > current_validations['max_items']):
raise ApiValueError(
"Invalid value for `%s`, number of items must be less than or "
@ -675,7 +711,8 @@ def check_validations(validations, input_variable_path, input_values):
)
)
if ('min_items' in current_validations and
if (is_json_validation_enabled('minItems', configuration) and
'min_items' in current_validations and
len(input_values) < current_validations['min_items']):
raise ValueError(
"Invalid value for `%s`, number of items must be greater than or "
@ -698,7 +735,8 @@ def check_validations(validations, input_variable_path, input_values):
max_val = input_values
min_val = input_values
if ('exclusive_maximum' in current_validations and
if (is_json_validation_enabled('exclusiveMaximum', configuration) and
'exclusive_maximum' in current_validations and
max_val >= current_validations['exclusive_maximum']):
raise ApiValueError(
"Invalid value for `%s`, must be a value less than `%s`" % (
@ -707,7 +745,8 @@ def check_validations(validations, input_variable_path, input_values):
)
)
if ('inclusive_maximum' in current_validations and
if (is_json_validation_enabled('maximum', configuration) and
'inclusive_maximum' in current_validations and
max_val > current_validations['inclusive_maximum']):
raise ApiValueError(
"Invalid value for `%s`, must be a value less than or equal to "
@ -717,7 +756,8 @@ def check_validations(validations, input_variable_path, input_values):
)
)
if ('exclusive_minimum' in current_validations and
if (is_json_validation_enabled('exclusiveMinimum', configuration) and
'exclusive_minimum' in current_validations and
min_val <= current_validations['exclusive_minimum']):
raise ApiValueError(
"Invalid value for `%s`, must be a value greater than `%s`" %
@ -727,7 +767,8 @@ def check_validations(validations, input_variable_path, input_values):
)
)
if ('inclusive_minimum' in current_validations and
if (is_json_validation_enabled('minimum', configuration) and
'inclusive_minimum' in current_validations and
min_val < current_validations['inclusive_minimum']):
raise ApiValueError(
"Invalid value for `%s`, must be a value greater than or equal "
@ -737,7 +778,8 @@ def check_validations(validations, input_variable_path, input_values):
)
)
flags = current_validations.get('regex', {}).get('flags', 0)
if ('regex' in current_validations and
if (is_json_validation_enabled('pattern', configuration) and
'regex' in current_validations and
not re.search(current_validations['regex']['pattern'],
input_values, flags=flags)):
err_msg = r"Invalid value for `%s`, must match regular expression `%s`" % (

View File

@ -20,8 +20,15 @@ import urllib3
import six
from six.moves import http_client as httplib
from petstore_api.exceptions import ApiValueError
JSON_SCHEMA_VALIDATION_KEYWORDS = {
'multipleOf', 'maximum', 'exclusiveMaximum',
'minimum', 'exclusiveMinimum', 'maxLength',
'minLength', 'pattern', 'maxItems', 'minItems'
}
class Configuration(object):
"""NOTE: This class is auto generated by OpenAPI Generator
@ -49,6 +56,19 @@ class Configuration(object):
then all undeclared properties received by the server are injected into the
additional properties map. In that case, there are undeclared properties, and
nothing to discard.
:param disabled_client_side_validations (string): Comma-separated list of
JSON schema validation keywords to disable JSON schema structural validation
rules. The following keywords may be specified: multipleOf, maximum,
exclusiveMaximum, minimum, exclusiveMinimum, maxLength, minLength, pattern,
maxItems, minItems.
By default, the validation is performed for data generated locally by the client
and data received from the server, independent of any validation performed by
the server side. If the input data does not satisfy the JSON schema validation
rules specified in the OpenAPI document, an exception is raised.
If disabled_client_side_validations is set, structural validation is
disabled. This can be useful to troubleshoot data validation problem, such as
when the OpenAPI document validation rules do not match the actual API data
received by the server.
:Example:
@ -94,6 +114,7 @@ conf = petstore_api.Configuration(
api_key=None, api_key_prefix=None,
username=None, password=None,
discard_unknown_keys=False,
disabled_client_side_validations="",
):
"""Constructor
"""
@ -124,6 +145,7 @@ conf = petstore_api.Configuration(
"""Password for HTTP basic authentication
"""
self.discard_unknown_keys = discard_unknown_keys
self.disabled_client_side_validations = disabled_client_side_validations
self.access_token = None
"""access token for OAuth/Bearer
"""
@ -205,6 +227,13 @@ conf = petstore_api.Configuration(
def __setattr__(self, name, value):
object.__setattr__(self, name, value)
if name == 'disabled_client_side_validations':
s = set(filter(None, value.split(',')))
for v in s:
if v not in JSON_SCHEMA_VALIDATION_KEYWORDS:
raise ApiValueError(
"Invalid keyword: '{0}''".format(v))
self._disabled_client_side_validations = s
@classmethod
def set_default(cls, default):

View File

@ -20,8 +20,15 @@ import urllib3
import six
from six.moves import http_client as httplib
from petstore_api.exceptions import ApiValueError
JSON_SCHEMA_VALIDATION_KEYWORDS = {
'multipleOf', 'maximum', 'exclusiveMaximum',
'minimum', 'exclusiveMinimum', 'maxLength',
'minLength', 'pattern', 'maxItems', 'minItems'
}
class Configuration(object):
"""NOTE: This class is auto generated by OpenAPI Generator
@ -49,6 +56,19 @@ class Configuration(object):
then all undeclared properties received by the server are injected into the
additional properties map. In that case, there are undeclared properties, and
nothing to discard.
:param disabled_client_side_validations (string): Comma-separated list of
JSON schema validation keywords to disable JSON schema structural validation
rules. The following keywords may be specified: multipleOf, maximum,
exclusiveMaximum, minimum, exclusiveMinimum, maxLength, minLength, pattern,
maxItems, minItems.
By default, the validation is performed for data generated locally by the client
and data received from the server, independent of any validation performed by
the server side. If the input data does not satisfy the JSON schema validation
rules specified in the OpenAPI document, an exception is raised.
If disabled_client_side_validations is set, structural validation is
disabled. This can be useful to troubleshoot data validation problem, such as
when the OpenAPI document validation rules do not match the actual API data
received by the server.
:Example:
@ -94,6 +114,7 @@ conf = petstore_api.Configuration(
api_key=None, api_key_prefix=None,
username=None, password=None,
discard_unknown_keys=False,
disabled_client_side_validations="",
):
"""Constructor
"""
@ -124,6 +145,7 @@ conf = petstore_api.Configuration(
"""Password for HTTP basic authentication
"""
self.discard_unknown_keys = discard_unknown_keys
self.disabled_client_side_validations = disabled_client_side_validations
self.access_token = None
"""access token for OAuth/Bearer
"""
@ -205,6 +227,13 @@ conf = petstore_api.Configuration(
def __setattr__(self, name, value):
object.__setattr__(self, name, value)
if name == 'disabled_client_side_validations':
s = set(filter(None, value.split(',')))
for v in s:
if v not in JSON_SCHEMA_VALIDATION_KEYWORDS:
raise ApiValueError(
"Invalid keyword: '{0}''".format(v))
self._disabled_client_side_validations = s
@classmethod
def set_default(cls, default):

View File

@ -249,7 +249,8 @@ class Endpoint(object):
check_validations(
self.validations,
(param,),
kwargs[param]
kwargs[param],
configuration=self.api_client.configuration
)
if kwargs['_check_input_type'] is False:

View File

@ -235,7 +235,8 @@ class Endpoint(object):
check_validations(
self.validations,
(param,),
kwargs[param]
kwargs[param],
configuration=self.api_client.configuration
)
if kwargs['_check_input_type'] is False:

View File

@ -2054,7 +2054,8 @@ class Endpoint(object):
check_validations(
self.validations,
(param,),
kwargs[param]
kwargs[param],
configuration=self.api_client.configuration
)
if kwargs['_check_input_type'] is False:

View File

@ -251,7 +251,8 @@ class Endpoint(object):
check_validations(
self.validations,
(param,),
kwargs[param]
kwargs[param],
configuration=self.api_client.configuration
)
if kwargs['_check_input_type'] is False:

View File

@ -1259,7 +1259,8 @@ class Endpoint(object):
check_validations(
self.validations,
(param,),
kwargs[param]
kwargs[param],
configuration=self.api_client.configuration
)
if kwargs['_check_input_type'] is False:

View File

@ -592,7 +592,8 @@ class Endpoint(object):
check_validations(
self.validations,
(param,),
kwargs[param]
kwargs[param],
configuration=self.api_client.configuration
)
if kwargs['_check_input_type'] is False:

View File

@ -1057,7 +1057,8 @@ class Endpoint(object):
check_validations(
self.validations,
(param,),
kwargs[param]
kwargs[param],
configuration=self.api_client.configuration
)
if kwargs['_check_input_type'] is False:

View File

@ -20,8 +20,15 @@ import urllib3
import six
from six.moves import http_client as httplib
from petstore_api.exceptions import ApiValueError
JSON_SCHEMA_VALIDATION_KEYWORDS = {
'multipleOf', 'maximum', 'exclusiveMaximum',
'minimum', 'exclusiveMinimum', 'maxLength',
'minLength', 'pattern', 'maxItems', 'minItems'
}
class Configuration(object):
"""NOTE: This class is auto generated by OpenAPI Generator
@ -49,6 +56,19 @@ class Configuration(object):
then all undeclared properties received by the server are injected into the
additional properties map. In that case, there are undeclared properties, and
nothing to discard.
:param disabled_client_side_validations (string): Comma-separated list of
JSON schema validation keywords to disable JSON schema structural validation
rules. The following keywords may be specified: multipleOf, maximum,
exclusiveMaximum, minimum, exclusiveMinimum, maxLength, minLength, pattern,
maxItems, minItems.
By default, the validation is performed for data generated locally by the client
and data received from the server, independent of any validation performed by
the server side. If the input data does not satisfy the JSON schema validation
rules specified in the OpenAPI document, an exception is raised.
If disabled_client_side_validations is set, structural validation is
disabled. This can be useful to troubleshoot data validation problem, such as
when the OpenAPI document validation rules do not match the actual API data
received by the server.
:param signing_info: Configuration parameters for the HTTP signature security scheme.
Must be an instance of petstore_api.signing.HttpSigningConfiguration
@ -135,6 +155,7 @@ conf = petstore_api.Configuration(
api_key=None, api_key_prefix=None,
username=None, password=None,
discard_unknown_keys=False,
disabled_client_side_validations="",
signing_info=None,
):
"""Constructor
@ -166,6 +187,7 @@ conf = petstore_api.Configuration(
"""Password for HTTP basic authentication
"""
self.discard_unknown_keys = discard_unknown_keys
self.disabled_client_side_validations = disabled_client_side_validations
if signing_info is not None:
signing_info.host = host
self.signing_info = signing_info
@ -252,6 +274,13 @@ conf = petstore_api.Configuration(
def __setattr__(self, name, value):
object.__setattr__(self, name, value)
if name == 'disabled_client_side_validations':
s = set(filter(None, value.split(',')))
for v in s:
if v not in JSON_SCHEMA_VALIDATION_KEYWORDS:
raise ApiValueError(
"Invalid keyword: '{0}''".format(v))
self._disabled_client_side_validations = s
if name == "signing_info" and value is not None:
# Ensure the host paramater from signing info is the same as
# Configuration.host.

View File

@ -112,7 +112,8 @@ class OpenApiModel(object):
check_validations(
self.validations,
(name,),
value
value,
self._configuration
)
self.__dict__['_data_store'][name] = value
@ -635,17 +636,50 @@ def check_allowed_values(allowed_values, input_variable_path, input_values):
)
def check_validations(validations, input_variable_path, input_values):
def is_json_validation_enabled(schema_keyword, configuration=None):
"""Returns true if JSON schema validation is enabled for the specified
validation keyword. This can be used to skip JSON schema structural validation
as requested in the configuration.
Args:
schema_keyword (string): the name of a JSON schema validation keyword.
configuration (Configuration): the configuration class.
"""
return (configuration is None or
not hasattr(configuration, '_disabled_client_side_validations') or
schema_keyword not in configuration._disabled_client_side_validations)
def check_validations(
validations, input_variable_path, input_values,
configuration=None):
"""Raises an exception if the input_values are invalid
Args:
validations (dict): the validation dictionary
input_variable_path (tuple): the path to the input variable
validations (dict): the validation dictionary.
input_variable_path (tuple): the path to the input variable.
input_values (list/str/int/float/date/datetime): the values that we
are checking
are checking.
configuration (Configuration): the configuration class.
"""
current_validations = validations[input_variable_path]
if ('max_length' in current_validations and
if (is_json_validation_enabled('multipleOf', configuration) and
'multiple_of' in current_validations and
isinstance(input_values, (int, float)) and
not (float(input_values) / current_validations['multiple_of']).is_integer()):
# Note 'multipleOf' will be as good as the floating point arithmetic.
raise ApiValueError(
"Invalid value for `%s`, value must be a multiple of "
"`%s`" % (
input_variable_path[0],
current_validations['multiple_of']
)
)
if (is_json_validation_enabled('maxLength', configuration) and
'max_length' in current_validations and
len(input_values) > current_validations['max_length']):
raise ApiValueError(
"Invalid value for `%s`, length must be less than or equal to "
@ -655,7 +689,8 @@ def check_validations(validations, input_variable_path, input_values):
)
)
if ('min_length' in current_validations and
if (is_json_validation_enabled('minLength', configuration) and
'min_length' in current_validations and
len(input_values) < current_validations['min_length']):
raise ApiValueError(
"Invalid value for `%s`, length must be greater than or equal to "
@ -665,7 +700,8 @@ def check_validations(validations, input_variable_path, input_values):
)
)
if ('max_items' in current_validations and
if (is_json_validation_enabled('maxItems', configuration) and
'max_items' in current_validations and
len(input_values) > current_validations['max_items']):
raise ApiValueError(
"Invalid value for `%s`, number of items must be less than or "
@ -675,7 +711,8 @@ def check_validations(validations, input_variable_path, input_values):
)
)
if ('min_items' in current_validations and
if (is_json_validation_enabled('minItems', configuration) and
'min_items' in current_validations and
len(input_values) < current_validations['min_items']):
raise ValueError(
"Invalid value for `%s`, number of items must be greater than or "
@ -698,7 +735,8 @@ def check_validations(validations, input_variable_path, input_values):
max_val = input_values
min_val = input_values
if ('exclusive_maximum' in current_validations and
if (is_json_validation_enabled('exclusiveMaximum', configuration) and
'exclusive_maximum' in current_validations and
max_val >= current_validations['exclusive_maximum']):
raise ApiValueError(
"Invalid value for `%s`, must be a value less than `%s`" % (
@ -707,7 +745,8 @@ def check_validations(validations, input_variable_path, input_values):
)
)
if ('inclusive_maximum' in current_validations and
if (is_json_validation_enabled('maximum', configuration) and
'inclusive_maximum' in current_validations and
max_val > current_validations['inclusive_maximum']):
raise ApiValueError(
"Invalid value for `%s`, must be a value less than or equal to "
@ -717,7 +756,8 @@ def check_validations(validations, input_variable_path, input_values):
)
)
if ('exclusive_minimum' in current_validations and
if (is_json_validation_enabled('exclusiveMinimum', configuration) and
'exclusive_minimum' in current_validations and
min_val <= current_validations['exclusive_minimum']):
raise ApiValueError(
"Invalid value for `%s`, must be a value greater than `%s`" %
@ -727,7 +767,8 @@ def check_validations(validations, input_variable_path, input_values):
)
)
if ('inclusive_minimum' in current_validations and
if (is_json_validation_enabled('minimum', configuration) and
'inclusive_minimum' in current_validations and
min_val < current_validations['inclusive_minimum']):
raise ApiValueError(
"Invalid value for `%s`, must be a value greater than or equal "
@ -737,7 +778,8 @@ def check_validations(validations, input_variable_path, input_values):
)
)
flags = current_validations.get('regex', {}).get('flags', 0)
if ('regex' in current_validations and
if (is_json_validation_enabled('pattern', configuration) and
'regex' in current_validations and
not re.search(current_validations['regex']['pattern'],
input_values, flags=flags)):
err_msg = r"Invalid value for `%s`, must match regular expression `%s`" % (

View File

@ -65,6 +65,7 @@ class FormatTest(ModelNormal):
('number',): {
'inclusive_maximum': 543.2,
'inclusive_minimum': 32.1,
'multiple_of': 32.5,
},
('password',): {
'max_length': 64,
@ -73,6 +74,7 @@ class FormatTest(ModelNormal):
('integer',): {
'inclusive_maximum': 100,
'inclusive_minimum': 10,
'multiple_of': 2,
},
('int32',): {
'inclusive_maximum': 200,

View File

@ -0,0 +1,131 @@
# coding: utf-8
# flake8: noqa
"""
Run the tests.
$ pip install nose (optional)
$ cd OpenAPIetstore-python
$ nosetests -v
"""
import os
import time
import atexit
import datetime
import json
import sys
import weakref
import unittest
from dateutil.parser import parse
from collections import namedtuple
import petstore_api
import petstore_api.configuration
HOST = 'http://petstore.swagger.io/v2'
MockResponse = namedtuple('MockResponse', 'data')
class ApiClientTests(unittest.TestCase):
def setUp(self):
self.api_client = petstore_api.ApiClient()
def test_configuration(self):
config = petstore_api.Configuration()
config.host = 'http://localhost/'
config.disabled_client_side_validations = ("multipleOf,maximum,exclusiveMaximum,minimum,exclusiveMinimum,"
"maxLength,minLength,pattern,maxItems,minItems")
with self.checkRaiseRegex(ValueError, "Invalid keyword: 'foo'"):
config.disabled_client_side_validations = 'foo'
config.disabled_client_side_validations = ""
def checkRaiseRegex(self, expected_exception, expected_regex):
if sys.version_info < (3, 0):
return self.assertRaisesRegexp(expected_exception, expected_regex)
return self.assertRaisesRegex(expected_exception, expected_regex)
def test_multiple_of(self):
inst = petstore_api.FormatTest(
byte='3',
date=datetime.date(2000, 1, 1),
password="abcdefghijkl",
integer=30,
number=65.0,
float=62.4
)
assert isinstance(inst, petstore_api.FormatTest)
with self.checkRaiseRegex(petstore_api.exceptions.ApiValueError, "Invalid value for `integer`, value must be a multiple of `2`"):
inst = petstore_api.FormatTest(
byte='3',
date=datetime.date(2000, 1, 1),
password="abcdefghijkl",
integer=31, # Value is supposed to be multiple of '2'. An error must be raised
number=65.0,
float=62.4
)
def test_multiple_of_deserialization(self):
data = {
'byte': '3',
'date': '1970-01-01',
'password': "abcdefghijkl",
'integer': 30,
'number': 65.0,
'float': 62.4,
}
response = MockResponse(data=json.dumps(data))
deserialized = self.api_client.deserialize(response, (petstore_api.FormatTest,), True)
self.assertTrue(isinstance(deserialized, petstore_api.FormatTest))
with self.checkRaiseRegex(petstore_api.exceptions.ApiValueError, "Invalid value for `integer`, value must be a multiple of `2`"):
data = {
'byte': '3',
'date': '1970-01-01',
'password': "abcdefghijkl",
'integer': 31, # Value is supposed to be multiple of '2'. An error must be raised
'number': 65.0,
'float': 62.4,
}
response = MockResponse(data=json.dumps(data))
deserialized = self.api_client.deserialize(response, (petstore_api.FormatTest,), True)
# Disable JSON schema validation. No error should be raised during deserialization.
config = petstore_api.Configuration()
config.disabled_client_side_validations = "multipleOf"
api_client = petstore_api.ApiClient(configuration=config)
data = {
'byte': '3',
'date': '1970-01-01',
'password': "abcdefghijkl",
'integer': 31, # Value is supposed to be multiple of '2'
'number': 65.0,
'float': 62.4,
}
response = MockResponse(data=json.dumps(data))
deserialized = api_client.deserialize(response, (petstore_api.FormatTest,), True)
self.assertTrue(isinstance(deserialized, petstore_api.FormatTest))
# Disable JSON schema validation but for a different keyword.
# An error should be raised during deserialization.
config = petstore_api.Configuration()
config.disabled_client_side_validations = "maxItems"
api_client = petstore_api.ApiClient(configuration=config)
with self.checkRaiseRegex(petstore_api.exceptions.ApiValueError, "Invalid value for `integer`, value must be a multiple of `2`"):
data = {
'byte': '3',
'date': '1970-01-01',
'password': "abcdefghijkl",
'integer': 31, # Value is supposed to be multiple of '2'
'number': 65.0,
'float': 62.4,
}
response = MockResponse(data=json.dumps(data))
deserialized = api_client.deserialize(response, (petstore_api.FormatTest,), True)

View File

@ -20,8 +20,15 @@ import urllib3
import six
from six.moves import http_client as httplib
from petstore_api.exceptions import ApiValueError
JSON_SCHEMA_VALIDATION_KEYWORDS = {
'multipleOf', 'maximum', 'exclusiveMaximum',
'minimum', 'exclusiveMinimum', 'maxLength',
'minLength', 'pattern', 'maxItems', 'minItems'
}
class Configuration(object):
"""NOTE: This class is auto generated by OpenAPI Generator
@ -49,6 +56,19 @@ class Configuration(object):
then all undeclared properties received by the server are injected into the
additional properties map. In that case, there are undeclared properties, and
nothing to discard.
:param disabled_client_side_validations (string): Comma-separated list of
JSON schema validation keywords to disable JSON schema structural validation
rules. The following keywords may be specified: multipleOf, maximum,
exclusiveMaximum, minimum, exclusiveMinimum, maxLength, minLength, pattern,
maxItems, minItems.
By default, the validation is performed for data generated locally by the client
and data received from the server, independent of any validation performed by
the server side. If the input data does not satisfy the JSON schema validation
rules specified in the OpenAPI document, an exception is raised.
If disabled_client_side_validations is set, structural validation is
disabled. This can be useful to troubleshoot data validation problem, such as
when the OpenAPI document validation rules do not match the actual API data
received by the server.
:param signing_info: Configuration parameters for the HTTP signature security scheme.
Must be an instance of petstore_api.signing.HttpSigningConfiguration
@ -135,6 +155,7 @@ conf = petstore_api.Configuration(
api_key=None, api_key_prefix=None,
username=None, password=None,
discard_unknown_keys=False,
disabled_client_side_validations="",
signing_info=None,
):
"""Constructor
@ -166,6 +187,7 @@ conf = petstore_api.Configuration(
"""Password for HTTP basic authentication
"""
self.discard_unknown_keys = discard_unknown_keys
self.disabled_client_side_validations = disabled_client_side_validations
if signing_info is not None:
signing_info.host = host
self.signing_info = signing_info
@ -252,6 +274,13 @@ conf = petstore_api.Configuration(
def __setattr__(self, name, value):
object.__setattr__(self, name, value)
if name == 'disabled_client_side_validations':
s = set(filter(None, value.split(',')))
for v in s:
if v not in JSON_SCHEMA_VALIDATION_KEYWORDS:
raise ApiValueError(
"Invalid keyword: '{0}''".format(v))
self._disabled_client_side_validations = s
if name == "signing_info" and value is not None:
# Ensure the host paramater from signing info is the same as
# Configuration.host.