Skip to content

Commit 632eb3e

Browse files
authored
Merge pull request #73 from p1c2u/feature/any-schema-type
Any schema type
2 parents 4731504 + 0cbbdb0 commit 632eb3e

File tree

6 files changed

+188
-10
lines changed

6 files changed

+188
-10
lines changed

openapi_core/schema/schemas/enums.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
class SchemaType(Enum):
66

7+
ANY = None
78
INTEGER = 'integer'
89
NUMBER = 'number'
910
STRING = 'string'

openapi_core/schema/schemas/exceptions.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,14 @@ class OpenAPISchemaError(OpenAPIMappingError):
55
pass
66

77

8+
class NoValidSchema(OpenAPISchemaError):
9+
pass
10+
11+
12+
class UndefinedItemsSchema(OpenAPISchemaError):
13+
pass
14+
15+
816
class InvalidSchemaValue(OpenAPISchemaError):
917
pass
1018

openapi_core/schema/schemas/factories.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ def __init__(self, dereferencer):
1616
def create(self, schema_spec):
1717
schema_deref = self.dereferencer.dereference(schema_spec)
1818

19-
schema_type = schema_deref.get('type', 'object')
19+
schema_type = schema_deref.get('type', None)
2020
schema_format = schema_deref.get('format')
2121
model = schema_deref.get('x-model', None)
2222
required = schema_deref.get('required', False)

openapi_core/schema/schemas/models.py

Lines changed: 28 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@
1010
from openapi_core.schema.schemas.enums import SchemaFormat, SchemaType
1111
from openapi_core.schema.schemas.exceptions import (
1212
InvalidSchemaValue, UndefinedSchemaProperty, MissingSchemaProperty,
13-
OpenAPISchemaError, NoOneOfSchema, MultipleOneOfSchema,
13+
OpenAPISchemaError, NoOneOfSchema, MultipleOneOfSchema, NoValidSchema,
14+
UndefinedItemsSchema,
1415
)
1516
from openapi_core.schema.schemas.util import (
1617
forcebool, format_date, format_datetime,
@@ -46,7 +47,7 @@ class Schema(object):
4647
}
4748

4849
TYPE_VALIDATOR_CALLABLE_GETTER = {
49-
None: lambda x: True,
50+
SchemaType.ANY: lambda x: x,
5051
SchemaType.BOOLEAN: TypeValidator(bool),
5152
SchemaType.INTEGER: TypeValidator(integer_types, exclude=bool),
5253
SchemaType.NUMBER: TypeValidator(integer_types, float, exclude=bool),
@@ -61,7 +62,7 @@ def __init__(
6162
schema_format=None, required=None, default=None, nullable=False,
6263
enum=None, deprecated=False, all_of=None, one_of=None,
6364
additional_properties=None):
64-
self.type = schema_type and SchemaType(schema_type)
65+
self.type = SchemaType(schema_type)
6566
self.model = model
6667
self.properties = properties and dict(properties) or {}
6768
self.items = items
@@ -107,7 +108,7 @@ def _get_all_required_properties(self):
107108

108109
return dict(
109110
(prop_name, val)
110-
for prop_name, val in all_properties.items()
111+
for prop_name, val in iteritems(all_properties)
111112
if prop_name in required
112113
)
113114

@@ -124,6 +125,7 @@ def get_cast_mapping(self):
124125
mapping = self.DEFAULT_CAST_CALLABLE_GETTER.copy()
125126
mapping.update({
126127
SchemaType.STRING: self._unmarshal_string,
128+
SchemaType.ANY: self._unmarshal_any,
127129
SchemaType.ARRAY: self._unmarshal_collection,
128130
SchemaType.OBJECT: self._unmarshal_object,
129131
})
@@ -137,9 +139,6 @@ def cast(self, value):
137139
raise InvalidSchemaValue("Null value for non-nullable schema")
138140
return self.default
139141

140-
if self.type is None:
141-
return value
142-
143142
cast_mapping = self.get_cast_mapping()
144143

145144
if self.type is not SchemaType.STRING and value == '':
@@ -156,8 +155,8 @@ def cast(self, value):
156155
def unmarshal(self, value):
157156
"""Unmarshal parameter from the value."""
158157
if self.deprecated:
159-
warnings.warn(
160-
"The schema is deprecated", DeprecationWarning)
158+
warnings.warn("The schema is deprecated", DeprecationWarning)
159+
161160
casted = self.cast(value)
162161

163162
if casted is None and not self.required:
@@ -190,7 +189,27 @@ def _unmarshal_string(self, value):
190189
value, self.format)
191190
)
192191

192+
def _unmarshal_any(self, value):
193+
types_resolve_order = [
194+
SchemaType.OBJECT, SchemaType.ARRAY, SchemaType.BOOLEAN,
195+
SchemaType.INTEGER, SchemaType.NUMBER, SchemaType.STRING,
196+
]
197+
cast_mapping = self.get_cast_mapping()
198+
for schema_type in types_resolve_order:
199+
cast_callable = cast_mapping[schema_type]
200+
try:
201+
return cast_callable(value)
202+
# @todo: remove ValueError when validation separated
203+
except (OpenAPISchemaError, TypeError, ValueError):
204+
continue
205+
206+
raise NoValidSchema(
207+
"No valid schema found for value {0}".format(value))
208+
193209
def _unmarshal_collection(self, value):
210+
if self.items is None:
211+
raise UndefinedItemsSchema("Undefined items' schema")
212+
194213
return list(map(self.items.unmarshal, value))
195214

196215
def _unmarshal_object(self, value, model_factory=None):

tests/integration/data/v3.0/petstore.yaml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,12 @@ paths:
168168
$ref: "#/components/responses/ErrorResponse"
169169
components:
170170
schemas:
171+
Utctime:
172+
oneOf:
173+
- type: string
174+
enum: [always, now]
175+
- type: string
176+
format: date-time
171177
Address:
172178
type: object
173179
x-model: Address
@@ -202,6 +208,7 @@ components:
202208
type: integer
203209
format: int64
204210
PetCreate:
211+
type: object
205212
x-model: PetCreate
206213
allOf:
207214
- $ref: "#/components/schemas/PetCreatePartOne"
@@ -284,6 +291,8 @@ components:
284291
required:
285292
- name
286293
properties:
294+
created:
295+
$ref: "#/components/schemas/Utctime"
287296
name:
288297
type: string
289298
TagList:

tests/integration/test_petstore.py

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
from openapi_core.schema.responses.models import Response
1818
from openapi_core.schema.schemas.exceptions import (
1919
UndefinedSchemaProperty, MissingSchemaProperty, NoOneOfSchema,
20+
NoValidSchema,
2021
)
2122
from openapi_core.schema.schemas.models import Schema
2223
from openapi_core.schema.servers.exceptions import InvalidServer
@@ -1032,3 +1033,143 @@ def test_post_tags_additional_properties(
10321033
assert response_result.data.message == message
10331034
assert response_result.data.rootCause == rootCause
10341035
assert response_result.data.additionalinfo == additionalinfo
1036+
1037+
def test_post_tags_created_now(
1038+
self, spec, response_validator):
1039+
host_url = 'http://petstore.swagger.io/v1'
1040+
path_pattern = '/v1/tags'
1041+
created = 'now'
1042+
pet_name = 'Dog'
1043+
data_json = {
1044+
'created': created,
1045+
'name': pet_name,
1046+
}
1047+
data = json.dumps(data_json)
1048+
1049+
request = MockRequest(
1050+
host_url, 'POST', '/tags',
1051+
path_pattern=path_pattern, data=data,
1052+
)
1053+
1054+
parameters = request.get_parameters(spec)
1055+
body = request.get_body(spec)
1056+
1057+
assert parameters == {}
1058+
assert isinstance(body, BaseModel)
1059+
assert body.created == created
1060+
assert body.name == pet_name
1061+
1062+
code = 400
1063+
message = 'Bad request'
1064+
rootCause = 'Tag already exist'
1065+
additionalinfo = 'Tag Dog already exist'
1066+
data_json = {
1067+
'code': 400,
1068+
'message': 'Bad request',
1069+
'rootCause': 'Tag already exist',
1070+
'additionalinfo': 'Tag Dog already exist',
1071+
}
1072+
data = json.dumps(data_json)
1073+
response = MockResponse(data, status_code=404)
1074+
1075+
response_result = response_validator.validate(request, response)
1076+
1077+
assert response_result.errors == []
1078+
assert isinstance(response_result.data, BaseModel)
1079+
assert response_result.data.code == code
1080+
assert response_result.data.message == message
1081+
assert response_result.data.rootCause == rootCause
1082+
assert response_result.data.additionalinfo == additionalinfo
1083+
1084+
def test_post_tags_created_datetime(
1085+
self, spec, response_validator):
1086+
host_url = 'http://petstore.swagger.io/v1'
1087+
path_pattern = '/v1/tags'
1088+
created = '2016-04-16T16:06:05Z'
1089+
pet_name = 'Dog'
1090+
data_json = {
1091+
'created': created,
1092+
'name': pet_name,
1093+
}
1094+
data = json.dumps(data_json)
1095+
1096+
request = MockRequest(
1097+
host_url, 'POST', '/tags',
1098+
path_pattern=path_pattern, data=data,
1099+
)
1100+
1101+
parameters = request.get_parameters(spec)
1102+
body = request.get_body(spec)
1103+
1104+
assert parameters == {}
1105+
assert isinstance(body, BaseModel)
1106+
assert body.created == created
1107+
assert body.name == pet_name
1108+
1109+
code = 400
1110+
message = 'Bad request'
1111+
rootCause = 'Tag already exist'
1112+
additionalinfo = 'Tag Dog already exist'
1113+
data_json = {
1114+
'code': code,
1115+
'message': message,
1116+
'rootCause': rootCause,
1117+
'additionalinfo': additionalinfo,
1118+
}
1119+
data = json.dumps(data_json)
1120+
response = MockResponse(data, status_code=404)
1121+
1122+
response_result = response_validator.validate(request, response)
1123+
1124+
assert response_result.errors == []
1125+
assert isinstance(response_result.data, BaseModel)
1126+
assert response_result.data.code == code
1127+
assert response_result.data.message == message
1128+
assert response_result.data.rootCause == rootCause
1129+
assert response_result.data.additionalinfo == additionalinfo
1130+
1131+
@pytest.mark.xfail(reason='OneOf for string not supported atm')
1132+
def test_post_tags_created_invalid_type(
1133+
self, spec, response_validator):
1134+
host_url = 'http://petstore.swagger.io/v1'
1135+
path_pattern = '/v1/tags'
1136+
created = 'long time ago'
1137+
pet_name = 'Dog'
1138+
data_json = {
1139+
'created': created,
1140+
'name': pet_name,
1141+
}
1142+
data = json.dumps(data_json)
1143+
1144+
request = MockRequest(
1145+
host_url, 'POST', '/tags',
1146+
path_pattern=path_pattern, data=data,
1147+
)
1148+
1149+
parameters = request.get_parameters(spec)
1150+
with pytest.raises(NoValidSchema):
1151+
request.get_body(spec)
1152+
1153+
assert parameters == {}
1154+
1155+
code = 400
1156+
message = 'Bad request'
1157+
rootCause = 'Tag already exist'
1158+
additionalinfo = 'Tag Dog already exist'
1159+
data_json = {
1160+
'code': code,
1161+
'message': message,
1162+
'rootCause': rootCause,
1163+
'additionalinfo': additionalinfo,
1164+
}
1165+
data = json.dumps(data_json)
1166+
response = MockResponse(data, status_code=404)
1167+
1168+
response_result = response_validator.validate(request, response)
1169+
1170+
assert response_result.errors == []
1171+
assert isinstance(response_result.data, BaseModel)
1172+
assert response_result.data.code == code
1173+
assert response_result.data.message == message
1174+
assert response_result.data.rootCause == rootCause
1175+
assert response_result.data.additionalinfo == additionalinfo

0 commit comments

Comments
 (0)