Skip to content

Commit 0b34ed1

Browse files
committed
New Feature: Validate with all properties required.
When setting the new flag `require_all_props` to true, it is simulated that all properties in the specs are required.
1 parent 0e30b71 commit 0b34ed1

File tree

6 files changed

+149
-53
lines changed

6 files changed

+149
-53
lines changed

openapi_core/schema/media_types/models.py

+11-3
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,8 @@ def deserialize(self, value):
3232
deserializer = self.get_dererializer()
3333
return deserializer(value)
3434

35-
def unmarshal(self, value, custom_formatters=None):
35+
def unmarshal(self, value, custom_formatters=None,
36+
require_all_props=False):
3637
if not self.schema:
3738
return value
3839

@@ -42,12 +43,19 @@ def unmarshal(self, value, custom_formatters=None):
4243
raise InvalidMediaTypeValue(exc)
4344

4445
try:
45-
unmarshalled = self.schema.unmarshal(deserialized, custom_formatters=custom_formatters)
46+
unmarshalled = self.schema.unmarshal(
47+
deserialized,
48+
custom_formatters=custom_formatters,
49+
require_all_props=require_all_props
50+
)
4651
except OpenAPISchemaError as exc:
4752
raise InvalidMediaTypeValue(exc)
4853

4954
try:
5055
return self.schema.validate(
51-
unmarshalled, custom_formatters=custom_formatters)
56+
unmarshalled,
57+
custom_formatters=custom_formatters,
58+
require_all_props=require_all_props
59+
)
5260
except OpenAPISchemaError as exc:
5361
raise InvalidMediaTypeValue(exc)

openapi_core/schema/parameters/models.py

+7-2
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,8 @@ def get_value(self, request):
8989

9090
return location[self.name]
9191

92-
def unmarshal(self, value, custom_formatters=None):
92+
def unmarshal(self, value, custom_formatters=None,
93+
require_all_props=False):
9394
if self.deprecated:
9495
warnings.warn(
9596
"{0} parameter is deprecated".format(self.name),
@@ -112,13 +113,17 @@ def unmarshal(self, value, custom_formatters=None):
112113
unmarshalled = self.schema.unmarshal(
113114
deserialized,
114115
custom_formatters=custom_formatters,
116+
require_all_props=require_all_props,
115117
strict=False,
116118
)
117119
except OpenAPISchemaError as exc:
118120
raise InvalidParameterValue(self.name, exc)
119121

120122
try:
121123
return self.schema.validate(
122-
unmarshalled, custom_formatters=custom_formatters)
124+
unmarshalled,
125+
custom_formatters=custom_formatters,
126+
require_all_props=require_all_props
127+
)
123128
except OpenAPISchemaError as exc:
124129
raise InvalidParameterValue(self.name, exc)

openapi_core/schema/schemas/models.py

+100-39
Original file line numberDiff line numberDiff line change
@@ -155,24 +155,42 @@ def get_all_required_properties_names(self):
155155

156156
return set(required)
157157

158-
def get_cast_mapping(self, custom_formatters=None, strict=True):
158+
def get_cast_mapping(self, custom_formatters=None, strict=True,
159+
require_all_props=False):
159160
primitive_unmarshallers = self.get_primitive_unmarshallers(
160-
custom_formatters=custom_formatters)
161+
custom_formatters=custom_formatters,
162+
require_all_props=require_all_props
163+
)
161164

162165
primitive_unmarshallers_partial = dict(
163-
(t, functools.partial(u, type_format=self.format, strict=strict))
166+
(t, functools.partial(
167+
u,
168+
type_format=self.format,
169+
strict=strict,
170+
require_all_props=require_all_props)
171+
)
164172
for t, u in primitive_unmarshallers.items()
165173
)
166174

167-
pass_defaults = lambda f: functools.partial(
168-
f, custom_formatters=custom_formatters, strict=strict)
175+
complex_unmarshallers = {
176+
SchemaType.ANY: self._unmarshal_any,
177+
SchemaType.ARRAY: self._unmarshal_collection,
178+
SchemaType.OBJECT: self._unmarshal_object,
179+
}
180+
181+
complex_unmarshallers_partial = dict(
182+
(t, functools.partial(
183+
u,
184+
custom_formatters=custom_formatters,
185+
strict=strict,
186+
require_all_props=require_all_props)
187+
)
188+
for t, u in complex_unmarshallers.items()
189+
)
190+
169191
mapping = self.DEFAULT_CAST_CALLABLE_GETTER.copy()
170192
mapping.update(primitive_unmarshallers_partial)
171-
mapping.update({
172-
SchemaType.ANY: pass_defaults(self._unmarshal_any),
173-
SchemaType.ARRAY: pass_defaults(self._unmarshal_collection),
174-
SchemaType.OBJECT: pass_defaults(self._unmarshal_object),
175-
})
193+
mapping.update(complex_unmarshallers_partial)
176194

177195
return defaultdict(lambda: lambda x: x, mapping)
178196

@@ -183,7 +201,8 @@ def are_additional_properties_allowed(self, one_of_schema=None):
183201
one_of_schema.additional_properties is not False)
184202
)
185203

186-
def cast(self, value, custom_formatters=None, strict=True):
204+
def cast(self, value, custom_formatters=None, strict=True,
205+
require_all_props=False):
187206
"""Cast value to schema type"""
188207
if value is None:
189208
if not self.nullable:
@@ -195,7 +214,8 @@ def cast(self, value, custom_formatters=None, strict=True):
195214
"Value {value} not in enum choices: {type}", value, self.enum)
196215

197216
cast_mapping = self.get_cast_mapping(
198-
custom_formatters=custom_formatters, strict=strict)
217+
custom_formatters=custom_formatters, strict=strict,
218+
require_all_props=require_all_props)
199219

200220
if self.type is not SchemaType.STRING and value == '':
201221
return None
@@ -210,12 +230,14 @@ def cast(self, value, custom_formatters=None, strict=True):
210230
raise InvalidSchemaValue(
211231
"Failed to cast value {value} to type {type}", value, self.type)
212232

213-
def unmarshal(self, value, custom_formatters=None, strict=True):
233+
def unmarshal(self, value, custom_formatters=None, strict=True,
234+
require_all_props=False):
214235
"""Unmarshal parameter from the value."""
215236
if self.deprecated:
216237
warnings.warn("The schema is deprecated", DeprecationWarning)
217238

218-
casted = self.cast(value, custom_formatters=custom_formatters, strict=strict)
239+
casted = self.cast(value, custom_formatters=custom_formatters, strict=strict,
240+
require_all_props=require_all_props)
219241

220242
if casted is None and not self.required:
221243
return None
@@ -242,7 +264,8 @@ def get_primitive_unmarshallers(self, **options):
242264

243265
return unmarshallers
244266

245-
def _unmarshal_any(self, value, custom_formatters=None, strict=True):
267+
def _unmarshal_any(self, value, custom_formatters=None, strict=True,
268+
require_all_props=False):
246269
types_resolve_order = [
247270
SchemaType.OBJECT, SchemaType.ARRAY, SchemaType.BOOLEAN,
248271
SchemaType.INTEGER, SchemaType.NUMBER, SchemaType.STRING,
@@ -252,7 +275,10 @@ def _unmarshal_any(self, value, custom_formatters=None, strict=True):
252275
result = None
253276
for subschema in self.one_of:
254277
try:
255-
casted = subschema.cast(value, custom_formatters)
278+
casted = subschema.cast(
279+
value, custom_formatters,
280+
require_all_props=require_all_props
281+
)
256282
except (OpenAPISchemaError, TypeError, ValueError):
257283
continue
258284
else:
@@ -277,7 +303,8 @@ def _unmarshal_any(self, value, custom_formatters=None, strict=True):
277303

278304
raise NoValidSchema(value)
279305

280-
def _unmarshal_collection(self, value, custom_formatters=None, strict=True):
306+
def _unmarshal_collection(self, value, custom_formatters=None, strict=True,
307+
require_all_props=False):
281308
if not isinstance(value, (list, tuple)):
282309
raise InvalidSchemaValue("Value {value} is not of type {type}", value, self.type)
283310

@@ -287,11 +314,13 @@ def _unmarshal_collection(self, value, custom_formatters=None, strict=True):
287314
f = functools.partial(
288315
self.items.unmarshal,
289316
custom_formatters=custom_formatters, strict=strict,
317+
require_all_props=require_all_props
290318
)
291319
return list(map(f, value))
292320

293321
def _unmarshal_object(self, value, model_factory=None,
294-
custom_formatters=None, strict=True):
322+
custom_formatters=None, strict=True,
323+
require_all_props=False):
295324
if not isinstance(value, (dict, )):
296325
raise InvalidSchemaValue("Value {value} is not of type {type}", value, self.type)
297326

@@ -302,7 +331,8 @@ def _unmarshal_object(self, value, model_factory=None,
302331
for one_of_schema in self.one_of:
303332
try:
304333
found_props = self._unmarshal_properties(
305-
value, one_of_schema, custom_formatters=custom_formatters)
334+
value, one_of_schema, custom_formatters=custom_formatters,
335+
require_all_props=False)
306336
except OpenAPISchemaError:
307337
pass
308338
else:
@@ -315,12 +345,14 @@ def _unmarshal_object(self, value, model_factory=None,
315345

316346
else:
317347
properties = self._unmarshal_properties(
318-
value, custom_formatters=custom_formatters)
348+
value, custom_formatters=custom_formatters,
349+
require_all_props=require_all_props)
319350

320351
return model_factory.create(properties, name=self.model)
321352

322353
def _unmarshal_properties(self, value, one_of_schema=None,
323-
custom_formatters=None, strict=True):
354+
custom_formatters=None, strict=True,
355+
require_all_props=False):
324356
all_props = self.get_all_properties()
325357
all_props_names = self.get_all_properties_names()
326358
all_req_props_names = self.get_all_required_properties_names()
@@ -344,7 +376,10 @@ def _unmarshal_properties(self, value, one_of_schema=None,
344376
for prop_name in extra_props:
345377
prop_value = value[prop_name]
346378
properties[prop_name] = self.additional_properties.unmarshal(
347-
prop_value, custom_formatters=custom_formatters)
379+
prop_value,
380+
custom_formatters=custom_formatters,
381+
require_all_props=require_all_props
382+
)
348383

349384
for prop_name, prop in iteritems(all_props):
350385
try:
@@ -357,12 +392,16 @@ def _unmarshal_properties(self, value, one_of_schema=None,
357392
prop_value = prop.default
358393
try:
359394
properties[prop_name] = prop.unmarshal(
360-
prop_value, custom_formatters=custom_formatters)
395+
prop_value,
396+
custom_formatters=custom_formatters,
397+
require_all_props=require_all_props
398+
)
361399
except OpenAPISchemaError as exc:
362400
raise InvalidSchemaProperty(prop_name, exc)
363401

364402
self._validate_properties(properties, one_of_schema=one_of_schema,
365-
custom_formatters=custom_formatters)
403+
custom_formatters=custom_formatters,
404+
require_all_props=require_all_props)
366405

367406
return properties
368407

@@ -380,7 +419,8 @@ def default(x, **kw):
380419

381420
return defaultdict(lambda: default, mapping)
382421

383-
def validate(self, value, custom_formatters=None):
422+
def validate(self, value, custom_formatters=None,
423+
require_all_props=False):
384424
if value is None:
385425
if not self.nullable:
386426
raise InvalidSchemaValue("Null value for non-nullable schema of type {type}", value, self.type)
@@ -396,11 +436,16 @@ def validate(self, value, custom_formatters=None):
396436
# structure validation
397437
validator_mapping = self.get_validator_mapping()
398438
validator_callable = validator_mapping[self.type]
399-
validator_callable(value, custom_formatters=custom_formatters)
439+
validator_callable(
440+
value,
441+
custom_formatters=custom_formatters,
442+
require_all_props=False
443+
)
400444

401445
return value
402446

403-
def _validate_collection(self, value, custom_formatters=None):
447+
def _validate_collection(self, value, custom_formatters=None,
448+
require_all_props=False):
404449
if self.items is None:
405450
raise UndefinedItemsSchema(self.type)
406451

@@ -428,10 +473,12 @@ def _validate_collection(self, value, custom_formatters=None):
428473
raise OpenAPISchemaError("Value may not contain duplicate items")
429474

430475
f = functools.partial(self.items.validate,
431-
custom_formatters=custom_formatters)
476+
custom_formatters=custom_formatters,
477+
require_all_props=require_all_props)
432478
return list(map(f, value))
433479

434-
def _validate_number(self, value, custom_formatters=None):
480+
def _validate_number(self, value, custom_formatters=None,
481+
require_all_props=False):
435482
if self.minimum is not None:
436483
if self.exclusive_minimum and value <= self.minimum:
437484
raise InvalidSchemaValue(
@@ -453,7 +500,8 @@ def _validate_number(self, value, custom_formatters=None):
453500
"Value {value} is not a multiple of {type}",
454501
value, self.multiple_of)
455502

456-
def _validate_string(self, value, custom_formatters=None):
503+
def _validate_string(self, value, custom_formatters=None,
504+
require_all_props=False):
457505
try:
458506
schema_format = SchemaFormat(self.format)
459507
except ValueError:
@@ -502,16 +550,19 @@ def _validate_string(self, value, custom_formatters=None):
502550

503551
return True
504552

505-
def _validate_object(self, value, custom_formatters=None):
553+
def _validate_object(self, value, custom_formatters=None,
554+
require_all_props=False):
506555
properties = value.__dict__
507556

508557
if self.one_of:
509558
valid_one_of_schema = None
510559
for one_of_schema in self.one_of:
511560
try:
512561
self._validate_properties(
513-
properties, one_of_schema,
514-
custom_formatters=custom_formatters)
562+
properties, one_of_schema,
563+
custom_formatters=custom_formatters,
564+
require_all_props=require_all_props
565+
)
515566
except OpenAPISchemaError:
516567
pass
517568
else:
@@ -523,8 +574,11 @@ def _validate_object(self, value, custom_formatters=None):
523574
raise NoOneOfSchema(self.type)
524575

525576
else:
526-
self._validate_properties(properties,
527-
custom_formatters=custom_formatters)
577+
self._validate_properties(
578+
properties,
579+
custom_formatters=custom_formatters,
580+
require_all_props=require_all_props
581+
)
528582

529583
if self.min_properties is not None:
530584
if self.min_properties < 0:
@@ -554,7 +608,8 @@ def _validate_object(self, value, custom_formatters=None):
554608
return True
555609

556610
def _validate_properties(self, value, one_of_schema=None,
557-
custom_formatters=None):
611+
custom_formatters=None,
612+
require_all_props=False):
558613
all_props = self.get_all_properties()
559614
all_props_names = self.get_all_properties_names()
560615
all_req_props_names = self.get_all_required_properties_names()
@@ -577,19 +632,25 @@ def _validate_properties(self, value, one_of_schema=None,
577632
for prop_name in extra_props:
578633
prop_value = value[prop_name]
579634
self.additional_properties.validate(
580-
prop_value, custom_formatters=custom_formatters)
635+
prop_value,
636+
custom_formatters=custom_formatters,
637+
require_all_props=require_all_props
638+
)
581639

582640
for prop_name, prop in iteritems(all_props):
583641
try:
584642
prop_value = value[prop_name]
585643
except KeyError:
586-
if prop_name in all_req_props_names:
644+
if (prop_name in all_req_props_names) or require_all_props:
587645
raise MissingSchemaProperty(prop_name)
588646
if not prop.nullable and not prop.default:
589647
continue
590648
prop_value = prop.default
591649
try:
592-
prop.validate(prop_value, custom_formatters=custom_formatters)
650+
prop.validate(prop_value,
651+
custom_formatters=custom_formatters,
652+
require_all_props=require_all_props
653+
)
593654
except OpenAPISchemaError as exc:
594655
raise InvalidSchemaProperty(prop_name, original_exception=exc)
595656

0 commit comments

Comments
 (0)