[python-experimental] Improve error message for regex matching, add unit tests. (#6226)

* Improve error message

* Add unit tests for regular expressions
This commit is contained in:
Sebastien Rosset
2020-05-11 03:18:34 -07:00
committed by GitHub
parent edb94f7ece
commit 4488db60b3
13 changed files with 126 additions and 27 deletions

View File

@@ -405,14 +405,15 @@ def check_validations(validations, input_variable_path, input_values):
if ('regex' in current_validations and
not re.search(current_validations['regex']['pattern'],
input_values, flags=flags)):
raise ApiValueError(
r"Invalid value for `%s`, must be a follow pattern or equal to "
r"`%s` with flags=`%s`" % (
input_variable_path[0],
current_validations['regex']['pattern'],
flags
)
)
err_msg = r"Invalid value for `%s`, must match regular expression `%s`" % (
input_variable_path[0],
current_validations['regex']['pattern']
)
if flags != 0:
# Don't print the regex flags if the flags are not
# specified in the OAS document.
err_msg = r"%s with flags=`%s`" % (err_msg, flags)
raise ApiValueError(err_msg)
def order_response_types(required_types):

View File

@@ -1808,6 +1808,10 @@ components:
properties:
cultivar:
type: string
pattern: ^[a-zA-Z\s]*$
origin:
type: string
pattern: /^[A-Z\s]*$/i
banana:
type: object
properties:

View File

@@ -671,14 +671,15 @@ def check_validations(validations, input_variable_path, input_values):
if ('regex' in current_validations and
not re.search(current_validations['regex']['pattern'],
input_values, flags=flags)):
raise ApiValueError(
r"Invalid value for `%s`, must be a follow pattern or equal to "
r"`%s` with flags=`%s`" % (
input_variable_path[0],
current_validations['regex']['pattern'],
flags
)
)
err_msg = r"Invalid value for `%s`, must match regular expression `%s`" % (
input_variable_path[0],
current_validations['regex']['pattern']
)
if flags != 0:
# Don't print the regex flags if the flags are not
# specified in the OAS document.
err_msg = r"%s with flags=`%s`" % (err_msg, flags)
raise ApiValueError(err_msg)
def order_response_types(required_types):

View File

@@ -4,6 +4,7 @@
Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
**cultivar** | **str** | | [optional]
**origin** | **str** | | [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

@@ -5,6 +5,7 @@ Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
**color** | **str** | | [optional]
**cultivar** | **str** | | [optional]
**origin** | **str** | | [optional]
**length_cm** | **float** | | [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

@@ -5,6 +5,7 @@ Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
**color** | **str** | | [optional]
**cultivar** | **str** | | [optional]
**origin** | **str** | | [optional]
**length_cm** | **float** | | [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

@@ -671,14 +671,15 @@ def check_validations(validations, input_variable_path, input_values):
if ('regex' in current_validations and
not re.search(current_validations['regex']['pattern'],
input_values, flags=flags)):
raise ApiValueError(
r"Invalid value for `%s`, must be a follow pattern or equal to "
r"`%s` with flags=`%s`" % (
input_variable_path[0],
current_validations['regex']['pattern'],
flags
)
)
err_msg = r"Invalid value for `%s`, must match regular expression `%s`" % (
input_variable_path[0],
current_validations['regex']['pattern']
)
if flags != 0:
# Don't print the regex flags if the flags are not
# specified in the OAS document.
err_msg = r"%s with flags=`%s`" % (err_msg, flags)
raise ApiValueError(err_msg)
def order_response_types(required_types):

View File

@@ -62,6 +62,17 @@ class Apple(ModelNormal):
}
validations = {
('cultivar',): {
'regex': {
'pattern': r'^[a-zA-Z\s]*$', # noqa: E501
},
},
('origin',): {
'regex': {
'pattern': r'^[A-Z\s]*$', # noqa: E501
'flags': (re.IGNORECASE)
},
},
}
additional_properties_type = None
@@ -78,6 +89,7 @@ class Apple(ModelNormal):
"""
return {
'cultivar': (str,), # noqa: E501
'origin': (str,), # noqa: E501
}
@cached_property
@@ -86,6 +98,7 @@ class Apple(ModelNormal):
attribute_map = {
'cultivar': 'cultivar', # noqa: E501
'origin': 'origin', # noqa: E501
}
_composed_schemas = {}
@@ -133,6 +146,7 @@ class Apple(ModelNormal):
through its discriminator because we passed in
_visited_composed_classes = (Animal,)
cultivar (str): [optional] # noqa: E501
origin (str): [optional] # noqa: E501
"""
self._data_store = {}

View File

@@ -72,6 +72,17 @@ class Fruit(ModelComposed):
}
validations = {
('cultivar',): {
'regex': {
'pattern': r'^[a-zA-Z\s]*$', # noqa: E501
},
},
('origin',): {
'regex': {
'pattern': r'^[A-Z\s]*$', # noqa: E501
'flags': (re.IGNORECASE)
},
},
}
additional_properties_type = None
@@ -89,6 +100,7 @@ class Fruit(ModelComposed):
return {
'color': (str,), # noqa: E501
'cultivar': (str,), # noqa: E501
'origin': (str,), # noqa: E501
'length_cm': (float,), # noqa: E501
}
@@ -99,6 +111,7 @@ class Fruit(ModelComposed):
attribute_map = {
'color': 'color', # noqa: E501
'cultivar': 'cultivar', # noqa: E501
'origin': 'origin', # noqa: E501
'length_cm': 'lengthCm', # noqa: E501
}
@@ -149,6 +162,7 @@ class Fruit(ModelComposed):
_visited_composed_classes = (Animal,)
color (str): [optional] # noqa: E501
cultivar (str): [optional] # noqa: E501
origin (str): [optional] # noqa: E501
length_cm (float): [optional] # noqa: E501
"""

View File

@@ -72,6 +72,17 @@ class GmFruit(ModelComposed):
}
validations = {
('cultivar',): {
'regex': {
'pattern': r'^[a-zA-Z\s]*$', # noqa: E501
},
},
('origin',): {
'regex': {
'pattern': r'^[A-Z\s]*$', # noqa: E501
'flags': (re.IGNORECASE)
},
},
}
additional_properties_type = None
@@ -89,6 +100,7 @@ class GmFruit(ModelComposed):
return {
'color': (str,), # noqa: E501
'cultivar': (str,), # noqa: E501
'origin': (str,), # noqa: E501
'length_cm': (float,), # noqa: E501
}
@@ -99,6 +111,7 @@ class GmFruit(ModelComposed):
attribute_map = {
'color': 'color', # noqa: E501
'cultivar': 'cultivar', # noqa: E501
'origin': 'origin', # noqa: E501
'length_cm': 'lengthCm', # noqa: E501
}
@@ -149,6 +162,7 @@ class GmFruit(ModelComposed):
_visited_composed_classes = (Animal,)
color (str): [optional] # noqa: E501
cultivar (str): [optional] # noqa: E501
origin (str): [optional] # noqa: E501
length_cm (float): [optional] # noqa: E501
"""

View File

@@ -111,6 +111,7 @@ class TestFruit(unittest.TestCase):
'color': [fruit],
'length_cm': [fruit, banana_instance],
'cultivar': [fruit],
'origin': [fruit],
}
)
# model._additional_properties_model_instances stores a list of
@@ -179,6 +180,7 @@ class TestFruit(unittest.TestCase):
'color': [fruit],
'length_cm': [fruit],
'cultivar': [fruit, apple_instance],
'origin': [fruit, apple_instance],
}
)
# model._additional_properties_model_instances stores a list of

View File

@@ -97,6 +97,7 @@ class TestGmFruit(unittest.TestCase):
'color': [fruit],
'length_cm': [fruit, banana_instance],
'cultivar': [fruit],
'origin': [fruit],
}
)
# model._additional_properties_model_instances stores a list of
@@ -157,6 +158,7 @@ class TestGmFruit(unittest.TestCase):
'color': [fruit],
'length_cm': [fruit, banana_instance],
'cultivar': [fruit, apple_instance],
'origin': [fruit, apple_instance],
}
)
@@ -164,7 +166,8 @@ class TestGmFruit(unittest.TestCase):
# apple test
color = 'red'
cultivar = 'golden delicious'
fruit = petstore_api.GmFruit(color=color, cultivar=cultivar)
origin = 'California'
fruit = petstore_api.GmFruit(color=color, cultivar=cultivar, origin=origin)
# check its properties
self.assertEqual(fruit.color, color)
self.assertEqual(fruit['color'], color)
@@ -172,12 +175,18 @@ class TestGmFruit(unittest.TestCase):
self.assertEqual(fruit.cultivar, cultivar)
self.assertEqual(fruit['cultivar'], cultivar)
self.assertEqual(getattr(fruit, 'cultivar'), cultivar)
self.assertEqual(fruit.origin, origin)
self.assertEqual(fruit['origin'], origin)
self.assertEqual(getattr(fruit, 'origin'), origin)
# check the dict representation
self.assertEqual(
fruit.to_dict(),
{
'color': color,
'cultivar': cultivar
'cultivar': cultivar,
'origin': origin,
}
)
@@ -198,6 +207,7 @@ class TestGmFruit(unittest.TestCase):
'color': [fruit],
'length_cm': [fruit],
'cultivar': [fruit, apple_instance],
'origin': [fruit, apple_instance],
}
)
# model._additional_properties_model_instances stores a list of

View File

@@ -92,6 +92,41 @@ class DeserializationTests(unittest.TestCase):
self.assertEqual(deserialized.color, color)
self.assertEqual(deserialized.breed, breed)
def test_regex_constraint(self):
"""
Test regex pattern validation.
"""
# Test with valid regex pattern.
inst = petstore_api.Apple(
cultivar="Akane"
)
assert isinstance(inst, petstore_api.Apple)
inst = petstore_api.Apple(
origin="cHiLe"
)
assert isinstance(inst, petstore_api.Apple)
# Test with invalid regex pattern.
err_msg = ("Invalid value for `{}`, must match regular expression `{}`$")
with self.assertRaisesRegexp(
petstore_api.ApiValueError,
err_msg.format("cultivar", "[^`]*")
):
inst = petstore_api.Apple(
cultivar="!@#%@$#Akane"
)
err_msg = ("Invalid value for `{}`, must match regular expression `{}` with flags")
with self.assertRaisesRegexp(
petstore_api.ApiValueError,
err_msg.format("origin", "[^`]*")
):
inst = petstore_api.Apple(
origin="!@#%@$#Chile"
)
def test_deserialize_mammal(self):
"""
deserialize mammal
@@ -125,4 +160,4 @@ class DeserializationTests(unittest.TestCase):
deserialized = self.deserialize(response, (petstore_api.Mammal,), True)
self.assertTrue(isinstance(deserialized, petstore_api.Zebra))
self.assertEqual(deserialized.type, zebra_type)
self.assertEqual(deserialized.class_name, class_name)
self.assertEqual(deserialized.class_name, class_name)