Coverage for jsonsubschema/_checkers.py : 75%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1'''
2Created on June 24, 2019
3@author: Andrew Habib
4'''
6import copy
7import json
8import math
9import sys
11import portion as I
12from greenery.lego import parse
14import jsonsubschema.config as config
16import jsonsubschema._constants as definitions
17import jsonsubschema._utils as utils
18from jsonsubschema._utils import print_db
21class UninhabitedMeta(type):
23 def __call__(cls, *args, **kwargs):
24 obj = type.__call__(cls, *args, **kwargs)
25 obj.updateInternalState()
26 obj.isUninhabited()
27 utils.validate_schema(obj)
28 return obj
31class JSONschema(dict, metaclass=UninhabitedMeta):
33 def __init__(self, *args, **kwargs):
35 super().__init__(*args, **kwargs)
37 # Since one might call the below constructor directly
38 # with a jsonschema as the constructor parameter,
39 # we also validate that the actual parameter after
40 # being build into a normal dict, is a valid schema.
41 utils.validate_schema(self)
43 # Instead of adding enum at every child constructor,
44 # do it here once and fir all.
45 if "enum" in self:
46 self.enum = self["enum"]
48 def updateInternalState(self):
49 pass
51 def isBoolean(self):
52 return self.keys() & definitions.Jconnectors
54 def hasEnum(self):
55 return "enum" in self.keys() or hasattr(self, "enum")
57 def isUninhabited(self):
58 # Don't store uninhabited key,
59 # but rather re-check on the fly to
60 # get an updated results based on the
61 # current internal state.
62 uninhabited = self._isUninhabited() # and (
63 # "enum" in self and not self["enum"])
64 if config.WARN_UNINHABITED and uninhabited:
65 print("Found an uninhabited type at: ", type(self), self)
66 return uninhabited
68 def meet(self, s):
69 #
70 # if self == s or is_top(s):
71 if is_top(s):
72 return self
73 #
74 if is_top(self):
75 return s
76 #
77 if is_bot(self) or is_bot(s):
78 return JSONbot()
79 #
80 ret = self._meet(s)
81 #
82 # if self.hasEnum() or s.hasEnum():
83 # enum = JSONschema.meet_enum(self, s)
84 # if enum:
85 # ret.enum = ret["enum"] = enum
86 # # ret["enum"] = list(enum)
87 # # ret.enum = ret["enum"]
88 # # instead of returning uninhabited type, return bot
89 # else:
90 # return JSONbot()
91 #
92 return ret
94 # @staticmethod
95 # def meet_enum(s1, s2):
96 # enum = set(s1.get("enum", [])) | set(s2.get("enum", []))
97 # valid_enum1 = utils.get_valid_enum_vals(enum, s1)
98 # valid_enum2 = utils.get_valid_enum_vals(enum, s2)
99 # return set(valid_enum1) & set(valid_enum2)
100 # return list(valid_enum1) + list(valid_enum2)
102 def meet_handle_rhs(self, s, meet_cb):
104 if s.type == "anyOf":
105 return JSONanyOf._meetAnyOf(s, self)
107 else:
108 return meet_cb(self, s)
110 def _join(self, s):
111 ''' Place holder in case a subclass does not implement its own join.
112 Should be removed once we are done fully implementing join '''
113 ret = {"anyOf": [self, s]}
114 # print("Eww! Using abstract _join :: running into corner case!")
115 return JSONanyOf(ret)
117 def join(self, s):
118 #
119 # if self == s or is_bot(s):
120 if is_bot(s):
121 return self
122 #
123 if is_bot(self):
124 return s
125 #
126 if is_top(self) or is_top(s): 126 ↛ 127line 126 didn't jump to line 127, because the condition on line 126 was never true
127 return JSONtop()
128 #
129 ret = self._join(s)
130 #
131 if self.hasEnum() and s.hasEnum():
132 enum = JSONschema.join_enum(self, s)
133 if enum: 133 ↛ 136line 133 didn't jump to line 136, because the condition on line 133 was never false
134 ret.enum = ret["enum"] = list(enum)
135 # instead of returning uninhabited types, return bot
136 if is_bot(ret): 136 ↛ 137line 136 didn't jump to line 137, because the condition on line 136 was never true
137 return JSONbot()
138 else:
139 return ret
141 @staticmethod
142 def join_enum(s1, s2):
143 if s1.type == s2.type: 143 ↛ exitline 143 didn't return from function 'join_enum', because the condition on line 143 was never false
144 try:
145 return sorted(set(s1.enum) | set(s2.enum))
146 except:
147 return s1.enum + s2.enum
149 def isSubtype(self, s):
150 #
151 # if self == s or is_bot(self) or is_top(s):
152 if is_bot(self) or is_top(s):
153 return True
154 #
155 if (not is_bot(self) and is_bot(s)) \
156 or (is_top(self) and not is_top(s)):
157 return False
158 #
159 return self.subtype_enum(s) and self._isSubtype(s)
161 def isSubtype_nonTrivial(self, s):
162 return self._isSubtype_nonTrivial(s)
164 def subtype_enum(self, s):
165 if self.hasEnum():
166 valid_enum = utils.get_valid_enum_vals(self.enum, s)
167 # no need to check individual elements
168 # as enum values are unique by definition
169 if len(valid_enum) == len(self.enum):
170 return True
171 else:
172 return False
173 else:
174 return True
176 def isSubtype_handle_rhs(self, s, isSubtype_cb):
178 if s.isBoolean():
179 if s.type == "anyOf": 179 ↛ 194line 179 didn't jump to line 194, because the condition on line 179 was never false
180 if not s.nonTrivialJoin:
181 return any(isSubtype_cb(self, i) for i in s.anyOf)
182 else:
183 return self.isSubtype_nonTrivial(s)
185 # elif s.type == "allOf":
186 # return all(isSubtype_cb(self, i) for i in s.allOf)
187 # elif s.type == "oneOf":
188 # return utils.one(isSubtype_cb(self, i) for i in s.oneOf)
189 # elif s.type == "not":
190 # # TODO
191 # print("No handling of 'not' on rhs yet.")
192 # return None
193 # else:
194 return isSubtype_cb(self, s)
197class JSONtop(JSONschema):
198 def __init__(self):
199 super().__init__({})
200 self.type = "top"
202 def _isUninhabited(self):
203 return False
205 def _meet(self, s):
206 return s
208 def _join(self, s):
209 return self
211 def _isSubtype(self, s):
213 def _isTopSubtype(s1, s2):
214 if is_top(s2):
215 return True
216 return False
218 super().isSubtype_handle_rhs(s, _isTopSubtype)
220 def __eq__(self, s):
221 if is_top(s):
222 return True
223 else:
224 return False
226 def __repr__(self):
227 return "JSON_TOP"
229 def __bool__(self):
230 return True
233def is_top(obj):
234 return obj == True or obj == {} or isinstance(obj, JSONtop)
237class JSONbot(JSONschema):
238 def __init__(self):
239 super().__init__({"not": {}})
240 self.type = "bot"
242 def _isUninhabited(self):
243 return True
245 def _meet(self, s):
246 return self
248 def _join(self, s):
249 return s
251 def _isSubtype(self, s):
253 def _isBotSubtype(s1, s2):
254 if is_bot(s2):
255 return True
256 return False
258 super().isSubtype_handle_rhs(s, _isBotSubtype)
260 def __eq__(self, s):
261 if is_bot(s):
262 return True
263 else:
264 return False
266 def __repr__(self):
267 return "JSON_BOT"
269 def __bool__(self):
270 return False
273def is_bot(obj):
274 return obj == False \
275 or (utils.is_dict(obj) and obj.get("not") == {}) \
276 or isinstance(obj, JSONbot) \
277 or (isinstance(obj, JSONschema) and obj.isUninhabited())
280class JSONTypeString(JSONschema):
282 def __init__(self, s):
283 super().__init__(s)
284 self.type = self["type"] = "string"
285 self.minLength = self.get("minLength", 0)
286 self.maxLength = self.get("maxLength", I.inf)
287 # json regexes are not anchored but the greenery library we use
288 # for regex inclusion assumes anchored regexes. So
289 # pad the regex with '.*' from both sides.
290 if "pattern" in s:
291 patrn = utils.regex_unanchor(s["pattern"])
292 self.pattern = utils.prepare_pattern_for_greenry(patrn)
293 else:
294 self.pattern = ""
296 def _isUninhabited(self):
297 return (self.minLength > self.maxLength)
298 # or self.pattern == None
299 # See comment below at updateInternalState()
300 # or self.range_with_pattern == None
302 def updateInternalState(self):
303 self.interval = I.closed(self.minLength, self.maxLength)
304 #
305 # Should be done here to check for uninhabited string schema
306 # But will defer it to the actual subtype check for performance...
307 # range = utils.string_range_to_regex(self.minLength, self.maxLength)
308 # self.range_with_pattern = utils.regex_meet(range, self.pattern)
310 def _meet(self, s):
312 def _meetString(s1, s2):
313 if s2.type == "string":
314 ret = {}
315 mn = max(s1.minLength, s2.minLength)
316 if utils.is_num(mn): 316 ↛ 318line 316 didn't jump to line 318, because the condition on line 316 was never false
317 ret["minLength"] = mn
318 mx = min(s1.maxLength, s2.maxLength)
319 if utils.is_num(mx):
320 ret["maxLength"] = mx
321 # Explicitly anchor pattern when assigned to the json key
322 # to reflect the greenery lib behavior on the json object.
323 patrn = utils.regex_meet(s1.pattern, s2.pattern)
324 if patrn:
325 ret["pattern"] = "^" + patrn + "$"
326 return JSONTypeString(ret)
327 else:
328 return JSONbot()
330 return super().meet_handle_rhs(s, _meetString)
332 def _join(self, s):
334 def _joinString(s1, s2):
335 if s2.type == "string":
336 ret = {}
337 mn = min(s1.minLength, s2.minLength)
338 if utils.is_num(mn): 338 ↛ 340line 338 didn't jump to line 340, because the condition on line 338 was never false
339 ret["minLength"] = mn
340 mx = max(s1.maxLength, s2.maxLength)
341 if utils.is_num(mx): 341 ↛ 342line 341 didn't jump to line 342, because the condition on line 341 was never true
342 ret["maxLength"] = mx
343 s1_range = utils.string_range_to_regex(
344 s1.minLength, s1.maxLength)
345 s2_range = utils.string_range_to_regex(
346 s2.minLength, s2.maxLength)
347 s1_new_pattern = utils.regex_meet(s1_range, s1.pattern)
348 s2_new_pattern = utils.regex_meet(s2_range, s2.pattern)
349 if s1_new_pattern and s2_new_pattern: 349 ↛ 352line 349 didn't jump to line 352, because the condition on line 349 was never false
350 ret["pattern"] = "^" + s1_new_pattern + \
351 "$|^" + s2_new_pattern + "$"
352 elif s1_new_pattern:
353 ret["pattern"] = "^" + s1_new_pattern + "$"
354 elif s2_new_pattern:
355 ret["pattern"] = "^" + s2_new_pattern + "$"
356 return JSONTypeString(ret)
357 else:
358 return JSONanyOf({"anyOf": [s1, s2]})
360 return _joinString(self, s)
362 def _isSubtype(self, s):
364 def _isStringSubtype(s1, s2):
365 if s2.type == "string":
366 is_sub_interval = s1.interval in s2.interval
367 if not is_sub_interval and not s1.pattern and not s2.pattern:
368 return False
369 #
370 if s1.pattern == s2.pattern:
371 return True
372 elif s1.hasEnum():
373 return super(JSONTypeString, s1).subtype_enum(s2)
374 else:
375 if s1.minLength == 0 and s1.maxLength == I.inf:
376 pattern1 = s1.pattern
377 else:
378 s1_range = utils.string_range_to_regex(
379 s1.minLength, s1.maxLength)
380 pattern1 = utils.regex_meet(s1_range, s1.pattern)
382 if s2.minLength == 0 and s2.maxLength == I.inf:
383 pattern2 = s2.pattern
384 else:
385 s2_range = utils.string_range_to_regex(
386 s2.minLength, s2.maxLength)
387 pattern2 = utils.regex_meet(s2_range, s2.pattern)
389 if utils.regex_isSubset(pattern1, pattern2):
390 return True
391 else:
392 return False
393 else:
394 return False
396 return super().isSubtype_handle_rhs(s, _isStringSubtype)
398 @staticmethod
399 def neg(s):
400 negated_strings = []
401 non_string = boolToConstructor.get("anyOf")(
402 {"anyOf": get_default_types_except("string")})
404 if "minLength" in s and s["minLength"] - 1 >= 0:
405 negated_strings.append(JSONTypeString(
406 {"maxLength": s["minLength"] - 1}))
407 if "maxLength" in s: 407 ↛ 408line 407 didn't jump to line 408, because the condition on line 407 was never true
408 negated_strings.append(JSONTypeString(
409 {"minLength": s["maxLength"] + 1}))
410 if "pattern" in s:
411 # Explicitly anchor pattern when assigned to the json key
412 # to reflect the greenery lib behavior on the json object.
413 patrn = utils.prepare_pattern_for_greenry(
414 utils.regex_unanchor(s["pattern"]))
415 negated_strings.append(JSONTypeString(
416 {"pattern": "^" + utils.complement_of_string_pattern(patrn) + "$"}))
418 if len(negated_strings) == 0:
419 return non_string
420 else:
421 joined_string = boolToConstructor.get(
422 "anyOf")({"anyOf": negated_strings})
423 return non_string.join(joined_string)
426class JSONTypeNumeric(JSONschema):
428 def __init__(self, s):
429 super().__init__(s)
430 self.minimum = self.get("minimum", -I.inf)
431 self.maximum = self.get("maximum", I.inf)
432 self.exclusiveMinimum = self.get("exclusiveMinimum", False)
433 self.exclusiveMaximum = self.get("exclusiveMaximum", False)
434 self.multipleOf = self.get("multipleOf", None)
436 def _isUninhabited(self):
437 return self.interval.empty \
438 or utils.is_num(self.multipleOf) and self.multipleOf > self.maximum
440 def updateInternalState(self):
441 self.build_interval_draft4()
443 def _meet(self, s):
445 def _meetNumeric(s1, s2):
446 if s1.type in definitions.Jnumeric and s2.type in definitions.Jnumeric:
447 ret = {}
449 mn = max(s1.minimum, s2.minimum)
450 if utils.is_num(mn):
451 ret["minimum"] = mn
453 mx = min(s1.maximum, s2.maximum)
454 if utils.is_num(mx):
455 ret["maximum"] = mx
457 mulOf = utils.lcm(s1.multipleOf, s2.multipleOf)
458 if mulOf:
459 ret["multipleOf"] = mulOf
461 if s1.type == s2.type == "number":
462 return JSONTypeNumber(ret)
463 else: # case one of them or both are integers
464 return JSONTypeInteger(ret)
466 else:
467 return JSONbot()
469 return super().meet_handle_rhs(s, _meetNumeric)
471 def _join(self, s):
472 # join integer with number
473 return JSONanyOf({"anyOf": [self, s]})
476class JSONTypeInteger(JSONTypeNumeric):
478 def __init__(self, s):
479 super().__init__(s)
480 self.type = self["type"] = "integer"
482 def build_interval_draft4(self):
483 # min, max, and interval attributes handle
484 # exclusive min/max as well as float values
485 # of min/max.
486 # All type operations such as meet, join,
487 # and subtype should rely only on interval
488 # and min/max attributes.
490 if self.exclusiveMinimum:
491 if utils.is_int_equiv(self.minimum): 491 ↛ 494line 491 didn't jump to line 494, because the condition on line 491 was never false
492 self.minimum = self.minimum + 1
493 else:
494 self.minimum = math.ceil(self.minimum)
495 elif utils.is_num(self.minimum):
496 self.minimum = math.ceil(self.minimum)
498 if self.exclusiveMaximum:
499 if utils.is_int_equiv(self.maximum): 499 ↛ 502line 499 didn't jump to line 502, because the condition on line 499 was never false
500 self.maximum = self.maximum - 1
501 else:
502 self.maximum = math.floor(self.maximum)
503 elif utils.is_num(self.maximum):
504 self.maximum = math.floor(self.maximum)
506 self.minimum, self.maximum = utils.get_new_min_max_with_mulof(
507 self.minimum, self.maximum, self.multipleOf)
509 self.interval = I.closed(self.minimum, self.maximum)
511 def _join(self, s):
513 def _joinInteger(s1, s2):
514 print_db("Trying to joinInteger")
515 if s2.type == "integer":
516 ret = {}
517 if utils.are_intervals_mergable(s1.interval, s2.interval):
518 if not s1.multipleOf and not s2.multipleOf:
519 joined_interval = s1.interval | s2.interval
520 if utils.is_num(joined_interval.lower):
521 ret["minimum"] = joined_interval.lower
522 if utils.is_num(joined_interval.upper):
523 ret["maximum"] = joined_interval.upper
524 return JSONTypeInteger(ret)
525 elif (s1.multipleOf and utils.is_interval_finite(s1.interval)) or \
526 (s2.multipleOf and utils.is_interval_finite(s2.interval)):
527 ret = JSONanyOf({"anyOf": [s1, s2]})
528 ret.nonTrivialJoin = True
529 return ret
530 # elif s2.type == "anyOf":
531 # import copy
532 # ret = copy.deepcopy(self)
533 # for i in s2.anyOf:
534 # ret = ret.join(i)
535 # return ret
537 print_db("NonTrivial is not set")
538 return JSONanyOf({"anyOf": [s1, s2]})
540 return _joinInteger(self, s)
542 def _isSubtype(self, s):
544 def _isIntegerSubtype(s1, s2):
545 if s2.type in definitions.Jnumeric:
546 if s1.hasEnum(): 546 ↛ 547line 546 didn't jump to line 547, because the condition on line 546 was never true
547 return super(JSONTypeInteger, s1).subtype_enum(s2)
548 #
549 is_sub_interval = s1.interval in s2.interval
550 if not is_sub_interval:
551 print_db("num__00")
552 return False
553 #
554 if (s1.multipleOf == s2.multipleOf) \
555 or (s1.multipleOf != None and s2.multipleOf == None) \
556 or (s1.multipleOf != None and s2.multipleOf != None and s1.multipleOf % s2.multipleOf == 0) \
557 or (s1.multipleOf == None and s2.multipleOf == 1):
558 print_db("num__01")
559 return True
560 # elif s2.type == "anyOf":
561 # return self._isSubtype_nonTrivial(s)
562 else:
563 return False
565 return super().isSubtype_handle_rhs(s, _isIntegerSubtype)
567 def _isSubtype_nonTrivial(self, s):
568 print_db("Nontrivial Integer subtype")
569 if s.type == "anyOf": 569 ↛ exitline 569 didn't return from function '_isSubtype_nonTrivial', because the condition on line 569 was never false
570 intervals = []
571 interval_to_mulofs = {}
572 for num_schema in s.anyOf:
573 if num_schema.interval not in interval_to_mulofs: 573 ↛ 576line 573 didn't jump to line 576, because the condition on line 573 was never false
574 interval_to_mulofs[num_schema.interval] = [
575 num_schema.multipleOf] if num_schema.multipleOf else []
576 elif num_schema.multipleOf:
577 interval_to_mulofs[num_schema.interval].append(
578 num_schema.multipleOf)
580 added = False
581 for j in list(intervals):
582 if utils.are_intervals_mergable(num_schema.interval, j): 582 ↛ 581line 582 didn't jump to line 581, because the condition on line 582 was never false
583 intervals.remove(j)
584 intervals.append((num_schema.interval | j).enclosure)
585 added = True
586 if not added:
587 intervals.append(num_schema.interval)
589 mulof = [self.multipleOf] if self.multipleOf else []
590 for x in utils.generate_range_with_multipleof(range(self.minimum, self.maximum+1), mulof, []):
591 for interv, m in interval_to_mulofs.items():
592 if x in interv:
593 if m:
594 if any(x % i == 0 for i in m if i != None):
595 break
596 else:
597 break
598 else:
599 return False
601 return True
603 @staticmethod
604 def neg(s):
605 negated_ints = []
606 non_ints = boolToConstructor.get("anyOf")(
607 {"anyOf": get_default_types_except("number", "integer")})
609 if "minimum" in s: 609 ↛ 615line 609 didn't jump to line 615, because the condition on line 609 was never false
610 # if "exclusiveMinimum":
611 negated_ints.append(JSONTypeInteger({"maximum": s["minimum"] - 1}))
612 # else:
613 # negated_numbers.append(JSONTypeNumber(
614 # {"maximum": s["minimum"], "exclusiveMaximum": True}))
615 if "maximum" in s: 615 ↛ 623line 615 didn't jump to line 623, because the condition on line 615 was never false
616 # if "exclusiveMaximum":
617 negated_ints.append(JSONTypeInteger({"minimum": s["maximum"] + 1}))
618 # else:
619 # negated_numbers.append(JSONTypeNumber(
620 # {"minimum": s["maximum"], "exclusiveMinimum": True}))
621 # TODO: No handling of multipleOf at the moment.
623 if len(negated_ints) == 0: 623 ↛ 624line 623 didn't jump to line 624, because the condition on line 623 was never true
624 return non_ints
625 else:
626 joined_ints = boolToConstructor.get(
627 "anyOf")({"anyOf": negated_ints})
628 return non_ints.join(joined_ints)
631class JSONTypeNumber(JSONTypeNumeric):
633 def __init__(self, s):
634 super().__init__(s)
635 self.type = self["type"] = "number"
637 def build_interval_draft4(self):
638 if self.exclusiveMinimum and self.exclusiveMaximum:
639 self.interval = I.open(self.minimum, self.maximum)
640 elif self.exclusiveMinimum:
641 self.interval = I.openclosed(self.minimum, self.maximum)
642 elif self.exclusiveMaximum:
643 self.interval = I.closedopen(self.minimum, self.maximum)
644 else:
645 self.interval = I.closed(self.minimum, self.maximum)
647 def _join(self, s):
649 def _joinNumber(s1, s2):
650 if s2.type in definitions.Jnumeric:
651 ret = {}
652 if s1.interval.overlaps(s2.interval): 652 ↛ 666line 652 didn't jump to line 666, because the condition on line 652 was never false
653 joined_interval = s1.interval | s2.interval
654 if utils.is_num(joined_interval.lower): 654 ↛ 655line 654 didn't jump to line 655, because the condition on line 654 was never true
655 ret["minimum"] = joined_interval.lower
656 if not joined_interval.left:
657 ret["exclusiveMinimum"] = True
658 if utils.is_num(joined_interval.upper): 658 ↛ 659line 658 didn't jump to line 659, because the condition on line 658 was never true
659 ret["maximum"] = joined_interval.upper
660 if not joined_interval.right:
661 ret["exclusiveMaximum"] = True
662 gcd = utils.gcd(s1.multipleOf, s2.multipleOf)
663 if utils.is_num(gcd) and gcd != 1: 663 ↛ 664line 663 didn't jump to line 664, because the condition on line 663 was never true
664 ret["multipleOf"] = gcd
665 else:
666 return JSONanyOf({"anyOf": [s1, s2]})
668 if s2.type == "integer":
669 ret = JSONTypeInteger(ret)
670 return JSONanyOf({"anyOf": [s1, ret]})
671 else:
672 return JSONTypeNumber(ret)
673 else:
674 return JSONanyOf({"anyOf": [s1, s2]})
676 return _joinNumber(self, s)
678 def _isSubtype(self, s):
680 def _isNumberSubtype(s1, s2):
681 if s2.type == "number":
682 if s1.hasEnum():
683 return super(JSONTypeNumber, s1).subtype_enum(s2)
684 is_sub_interval = s1.interval in s2.interval
685 if not is_sub_interval:
686 print_db("num__00")
687 return False
688 #
689 if (s1.multipleOf == s2.multipleOf) \
690 or (s1.multipleOf != None and s2.multipleOf == None) \
691 or (s1.multipleOf != None and s2.multipleOf != None and s1.multipleOf % s2.multipleOf == 0) \
692 or (utils.is_int_equiv(s1.multipleOf) and s2.multipleOf == None):
693 print_db("num__01")
694 return True
695 elif s2.type == "integer":
696 is_sub_interval = s1.interval in s2.interval
697 if not is_sub_interval:
698 print_db("num__02")
699 return False
700 #
701 if utils.is_int_equiv(s1.multipleOf) and \
702 (s2.multipleOf == None or ((s1.multipleOf != None and s2.multipleOf != None and s1.multipleOf % s2.multipleOf == 0))):
703 print_db("num__03")
704 return True
705 else:
706 print_db("num__04")
707 return False
709 return super().isSubtype_handle_rhs(s, _isNumberSubtype)
711 @staticmethod
712 def neg(s):
713 negated_numbers = []
714 non_numbers = boolToConstructor.get("anyOf")(
715 {"anyOf": get_default_types_except("number", "integer")})
717 if "minimum" in s: 717 ↛ 719line 717 didn't jump to line 719, because the condition on line 717 was never true
718 if "exclusiveMinimum":
719 negated_numbers.append(
720 JSONTypeNumber({"maximum": s["minimum"]}))
721 else:
722 negated_numbers.append(JSONTypeNumber(
723 {"maximum": s["minimum"], "exclusiveMaximum": True}))
724 if "maximum" in s: 724 ↛ 726line 724 didn't jump to line 726, because the condition on line 724 was never true
725 if "exclusiveMaximum":
726 negated_numbers.append(
727 JSONTypeNumber({"minimum": s["maximum"]}))
728 else:
729 negated_numbers.append(JSONTypeNumber(
730 {"minimum": s["maximum"], "exclusiveMinimum": True}))
731 # TODO: No handling of multipleOf at the moment.
733 if len(negated_numbers) == 0: 733 ↛ 736line 733 didn't jump to line 736, because the condition on line 733 was never false
734 return non_numbers
735 else:
736 joined_numbers = boolToConstructor.get(
737 "anyOf")({"anyOf": negated_numbers})
738 return non_numbers.join(joined_numbers)
741class JSONTypeBoolean(JSONschema):
743 def __init__(self, s):
744 super().__init__(s)
745 self.type = self["type"] = "boolean"
746 # _enum = self.get("enum")
747 # if _enum and len(_enum) == 2:
748 # del self[_enum]
750 def _isUninhabited(self):
751 return False
753 def _meet(self, s):
755 def _meetBoolean(s1, s2):
756 if s2.type == "boolean":
757 if s1.hasEnum() and s2.hasEnum():
758 _overlap = set(s1.enum).intersection(s2.enum)
759 if _overlap:
760 return JSONTypeBoolean({"enum": list(_overlap)})
761 else:
762 return JSONbot()
763 elif s1.hasEnum():
764 return JSONTypeBoolean({"enum": s1.enum})
765 elif s2.hasEnum(): 765 ↛ 766line 765 didn't jump to line 766, because the condition on line 765 was never true
766 return JSONTypeBoolean({"enum": s2.enum})
767 else:
768 return JSONTypeBoolean({})
769 else:
770 return JSONbot()
772 return super().meet_handle_rhs(s, _meetBoolean)
774 def _isSubtype(self, s):
776 def _isBooleanSubtype(self, s2):
777 if s2.type == "boolean":
778 return True
779 else:
780 return False
782 return super().isSubtype_handle_rhs(s, _isBooleanSubtype)
784 @staticmethod
785 def neg(s):
786 negated_boolean = []
787 non_boolean = boolToConstructor.get("anyOf")(
788 {"anyOf": get_default_types_except("boolean")})
790 # booleans are allowed to keep enums, so check if any
791 _enum = s.get("enum")
792 if _enum:
793 if len(_enum) == 1: # exactly negating one value, return the other 793 ↛ 797line 793 didn't jump to line 797, because the condition on line 793 was never false
794 # negated_boolean.append(JSONTypeBoolean({"enum": [not _enum[0]]}))
795 return non_boolean.join(JSONTypeBoolean({"enum": [not _enum[0]]}))
797 return non_boolean
800class JSONTypeNull(JSONschema):
802 def __init__(self, s):
803 super().__init__(s)
804 self.type = self["type"] = "null"
806 def _isUninhabited(self):
807 return False
809 def _meet(self, s):
811 def _meetNull(s1, s2):
813 if s2.type == "null":
814 return s1
815 else:
816 return JSONbot()
818 return super().meet_handle_rhs(s, _meetNull)
820 def _isSubtype(self, s):
822 def _isNullSubtype(self, s2):
823 if s2.type == "null":
824 return True
825 else:
826 return False
828 return super().isSubtype_handle_rhs(s, _isNullSubtype)
830 @staticmethod
831 def neg(s):
832 return boolToConstructor.get("anyOf")(
833 {"anyOf": get_default_types_except("null")})
836class JSONTypeArray(JSONschema):
838 def __init__(self, s):
839 super().__init__(s)
840 self.type = self["type"] = "array"
841 self.minItems = self.get("minItems", 0)
842 self.maxItems = self.get("maxItems", I.inf)
843 self.items_ = self.get("items", JSONtop())
844 self.additionalItems = self.get("additionalItems", True)
845 self.uniqueItems = self.get("uniqueItems", False)
847 def compute_actual_maxItems(self):
848 if utils.is_list(self.items_) and is_bot(self.additionalItems):
849 new_max = min(self.maxItems, len(self.items_))
850 if new_max != self.maxItems: 850 ↛ exitline 850 didn't return from function 'compute_actual_maxItems', because the condition on line 850 was never false
851 self.maxItems = new_max
853 def _isUninhabited(self):
854 return (self.minItems > self.maxItems) or \
855 (utils.is_list(self.items_) and self.additionalItems ==
856 False and self.minItems > len(self.items_)) or \
857 (utils.is_list(self.items_) and len(self.items_) == 0)
859 def updateInternalState(self):
860 self.compute_actual_maxItems()
861 self.interval = I.closed(self.minItems, self.maxItems)
862 if utils.is_list(self.items_) and len(self.items_) == self.maxItems:
863 self.additionalItems = False
865 def _meet(self, s):
867 def _meetArray(s1, s2):
868 if s2.type == "array":
869 # ret = {}
870 # ret["type"] = "array"
871 # ret["minItems"] = max(s1.minItems, s2.minItems)
872 # ret["maxItems"] = min(s1.maxItems, s2.maxItems)
873 # ret["uniqueItems"] = s1.uniqueItems or s2.uniqueItems
874 ret = JSONTypeArray({})
875 # ret["type"] = "array"
876 ret.minItems = max(s1.minItems, s2.minItems)
877 ret.maxItems = min(s1.maxItems, s2.maxItems)
878 ret.uniqueItems = s1.uniqueItems or s2.uniqueItems
880 def meet_arrayItems_dict_list(s1, s2, ret):
881 assert utils.is_dict(s1.items_) and utils.is_list(
882 s2.items_), "Violating meet_arrayItems_dict_list condition: 's1.items is dict' and 's2.items is list'"
884 itms = []
885 for i in s2.items_:
886 r = i.meet(s1.items_)
887 if not (is_bot(r) or r.isUninhabited()):
888 itms.append(r)
889 else:
890 break
892 ret.items_ = itms
894 if s2.additionalItems == True:
895 ret.additionalItems = copy.deepcopy(s1.items_)
896 elif s2.additionalItems == False:
897 ret.additionalItems = False
898 elif utils.is_dict(s2.additionalItems):
899 addItms = s2.additionalItems.meet(s1.items_)
900 ret.additionalItems = False if is_bot(
901 addItms) else addItms
902 return ret
904 if utils.is_dict(s1.items_): 904 ↛ 912line 904 didn't jump to line 912, because the condition on line 904 was never false
906 if utils.is_dict(s2.items_): 906 ↛ 909line 906 didn't jump to line 909, because the condition on line 906 was never false
907 ret.items = s1.items_.meet(s2.items_)
909 elif utils.is_list(s2.items_):
910 ret = meet_arrayItems_dict_list(s1, s2, ret)
912 elif utils.is_list(s1.items_):
914 if utils.is_dict(s2.items_):
915 ret = meet_arrayItems_dict_list(s2, s1, ret)
917 elif utils.is_list(s2.items_):
918 self_len = len(s1.items_)
919 s_len = len(s2.items_)
921 def meet_arrayAdditionalItems_list_list(s1, s2):
922 if utils.is_bool(s1.additionalItems) and utils.is_bool(s2.additionalItems):
923 ad = s1.additionalItems and s2.additionalItems
924 elif utils.is_dict(s1.additionalItems):
925 ad = s1.additionalItems.meet(
926 s2.additionalItems)
927 elif utils.is_dict(s2.additionalItems):
928 ad = s2.additionalItems.meet(
929 s1.additionalItems)
930 return False if is_bot(ad) else ad
932 def meet_array_longlist_shorterlist(s1, s2, ret):
933 s1_len = len(s1.items_)
934 s2_len = len(s2.items_)
935 assert s1_len > s2_len, "Violating meet_array_longlist_shorterlist condition: 's1.len > s2.len'"
936 itms = []
937 for i, j in zip(s1.items_, s2.items_):
938 r = i.meet(j)
939 if not (is_bot(r) or r.isUninhabited()):
940 itms.append(r)
941 else:
942 ad = False
943 break
944 else:
945 for i in range(s2_len, s1_len):
946 r = s1.items_[i].meet(s2.additionalItems)
947 if not (is_bot(r) or r.isUninhabited()):
948 itms.append(r)
949 else:
950 ad = False
951 break
952 else:
953 ad = meet_arrayAdditionalItems_list_list(
954 s1, s2)
956 ret.additionalItems = ad
957 ret.items_ = itms
958 return ret
960 if self_len == s_len:
961 itms = []
962 for i, j in zip(s1.items_, s2.items_):
963 r = i.meet(j)
964 if not (is_bot(r) or r.isUninhabited()):
965 itms.append(r)
966 else:
967 ad = False
968 break
969 else:
970 ad = meet_arrayAdditionalItems_list_list(
971 s1, s2)
973 ret.additionalItems = ad
974 ret.items_ = itms
976 elif self_len > s_len:
977 ret = meet_array_longlist_shorterlist(s1, s2, ret)
979 elif self_len < s_len:
980 ret = meet_array_longlist_shorterlist(s2, s1, ret)
981 ret.updateInternalState()
982 return ret
984 else:
985 return JSONbot()
987 return super().meet_handle_rhs(s, _meetArray)
989 def _isSubtype(self, s):
991 def _isArraySubtype(s1, s2):
992 if s2.type != "array":
993 return False
994 if s1.hasEnum(): 994 ↛ 995line 994 didn't jump to line 995, because the condition on line 994 was never true
995 return super(JSONTypeArray, s1).subtype_enum(s2)
996 #
997 # -- minItems and maxItems
998 is_sub_interval = s1.interval in s2.interval
999 if not is_sub_interval:
1000 print_db("__01__")
1001 return False
1002 #
1003 # -- uniqueItemsue
1004 # TODO Double-check. Could be more subtle?
1005 if not s1.uniqueItems and s2.uniqueItems:
1006 print_db("__02__")
1007 return False
1008 #
1009 # -- items = {not empty}
1010 # no need to check additionalItems
1011 if utils.is_dict(s1.items_):
1012 if utils.is_dict(s2.items_):
1013 print_db(s1.items_)
1014 print_db(s2.items_)
1015 if s1.items_.isSubtype(s2.items_):
1016 print_db("__05__")
1017 return True
1018 else:
1019 print_db("__06__")
1020 return False
1021 elif utils.is_list(s2.items_): 1021 ↛ exitline 1021 didn't return from function '_isArraySubtype', because the condition on line 1021 was never false
1022 if s2.additionalItems == False: 1022 ↛ 1023line 1022 didn't jump to line 1023, because the condition on line 1022 was never true
1023 print_db("__07__")
1024 return False
1025 elif s2.additionalItems == True: 1025 ↛ 1032line 1025 didn't jump to line 1032, because the condition on line 1025 was never false
1026 for i in s2.items_:
1027 if not s1.items_.isSubtype(i): 1027 ↛ 1028line 1027 didn't jump to line 1028, because the condition on line 1027 was never true
1028 print_db("__08__")
1029 return False
1030 print_db("__09__")
1031 return True
1032 elif utils.is_dict(s2.additionalItems):
1033 for i in s2.items_:
1034 if not s1.items_.isSubtype(i):
1035 print_db("__10__")
1036 return False
1037 print_db(type(s1.items_), s1.items_)
1038 print_db(type(s2.additionalItems),
1039 s2.additionalItems)
1040 if s1.items_.isSubtype(s2.additionalItems):
1041 print_db("__11__")
1042 return True
1043 else:
1044 print_db("__12__")
1045 return False
1046 #
1047 elif utils.is_list(s1.items_): 1047 ↛ exitline 1047 didn't return from function '_isArraySubtype', because the condition on line 1047 was never false
1048 print_db("lhs is list")
1049 if utils.is_dict(s2.items_):
1050 if s1.additionalItems == False:
1051 for i in s1.items_:
1052 if not i.isSubtype(s2.items_):
1053 print_db("__13__")
1054 return False
1055 print_db("__14__")
1056 return True
1057 elif s1.additionalItems == True: 1057 ↛ 1067line 1057 didn't jump to line 1067, because the condition on line 1057 was never false
1058 for i in s1.items_:
1059 if not i.isSubtype(s2.items_): 1059 ↛ 1060line 1059 didn't jump to line 1060, because the condition on line 1059 was never true
1060 return False
1061 # since s1.additional items is True,
1062 # then TOP should also be a subtype of
1063 # s2.items
1064 if JSONtop().isSubtype(s2.items_):
1065 return True
1066 return False
1067 elif utils.is_dict(s1.additionalItems):
1068 for i in s1.items_:
1069 if not i.isSubtype(s2.items_):
1070 return False
1071 if s1.additionalItems.isSubtype(s2.items_):
1072 return True
1073 else:
1074 return False
1075 # now lhs and rhs are lists
1076 elif utils.is_list(s2.items_): 1076 ↛ exitline 1076 didn't return from function '_isArraySubtype', because the condition on line 1076 was never false
1077 print_db("lhs & rhs are lists")
1078 len1 = len(s1.items_)
1079 len2 = len(s2.items_)
1080 for i, j in zip(s1.items_, s2.items_):
1081 if not i.isSubtype(j): 1081 ↛ 1082line 1081 didn't jump to line 1082, because the condition on line 1081 was never true
1082 return False
1083 if len1 == len2: 1083 ↛ 1084line 1083 didn't jump to line 1084, because the condition on line 1083 was never true
1084 print_db("len1 == len2")
1085 if s1.additionalItems == s2.additionalItems:
1086 return True
1087 elif s1.additionalItems == True and s2.additionalItems == False:
1088 return False
1089 elif s1.additionalItems == False and s2.additionalItems == True:
1090 return True
1091 else:
1092 return s1.additionalItems.isSubtype(s2.additionalItems)
1093 elif len1 > len2:
1094 diff = len1 - len2
1095 for i in range(len1-diff, len1): 1095 ↛ 1103line 1095 didn't jump to line 1103, because the loop on line 1095 didn't complete
1096 if s2.additionalItems == False: 1096 ↛ 1097line 1096 didn't jump to line 1097, because the condition on line 1096 was never true
1097 return False
1098 elif s2.additionalItems == True: 1098 ↛ 1100line 1098 didn't jump to line 1100, because the condition on line 1098 was never false
1099 return True
1100 elif not s1.items_[i].isSubtype(s2.additionalItems):
1101 print_db("9999")
1102 return False
1103 print_db("8888")
1104 return True
1105 else: # len2 > len 1
1106 diff = len2 - len1
1107 for i in range(len2 - diff, len2): 1107 ↛ 1114line 1107 didn't jump to line 1114, because the loop on line 1107 didn't complete
1108 if s1.additionalItems == False:
1109 return True
1110 elif s1.additionalItems == True: 1110 ↛ 1112line 1110 didn't jump to line 1112, because the condition on line 1110 was never false
1111 return False
1112 elif not s1.additionalItems.isSubtype(s2.items_[i]):
1113 return False
1114 return s1.additionalItems.isSubtype(s2.additionalItems)
1116 return super().isSubtype_handle_rhs(s, _isArraySubtype)
1118 @staticmethod
1119 def neg(s):
1120 # for k, default in JSONTypeArray.kw_defaults.items():
1121 # if s.__getattr__(k) != default:
1122 # break
1123 # else:
1124 return None
1127class JSONTypeObject(JSONschema):
1129 def __init__(self, s):
1130 super().__init__(s)
1131 self.type = self["type"] = "object"
1132 self.properties = self.get("properties", {})
1133 self.additionalProperties = self.get("additionalProperties", JSONtop())
1134 self.required = self.get("required", [])
1135 self.minProperties = self.get("minProperties", 0)
1136 self.maxProperties = self.get("maxProperties", I.inf)
1137 self.patternProperties = {}
1138 if "patternProperties" in self:
1139 for k, v in self["patternProperties"].items():
1140 self.patternProperties[utils.regex_unanchor(k)] = v
1142 def compute_actual_min_max_Properties(self):
1144 new_min = max(self.minProperties, len(self.required))
1145 if new_min != self.minProperties:
1146 self.minProperties = new_min
1148 # if is_bot(self.additionalProperties): # This is wrong because of patternProperties
1149 # new_max = min(self.maxProperties, len(self.properties))
1150 # if new_max != self.maxProperties:
1151 # self.maxProperties = new_max
1153 def _isUninhabited(self):
1155 def required_is_uninhabited(s):
1156 ''' checks if every required key is actually allowed
1157 by the key restrictions '''
1158 if s.additionalProperties:
1159 return False
1161 for k in s.required:
1162 if not k in s.properties.keys(): 1162 ↛ 1163line 1162 didn't jump to line 1163, because the condition on line 1162 was never true
1163 for k_ in s.patternProperties.keys():
1164 if utils.regex_matches_string(k_, k):
1165 break
1166 else:
1167 # here, inner loop finished and key was not found;
1168 # so it is uninhabited because a required key is not allowed
1169 return True
1171 return False
1173 return self.minProperties > self.maxProperties \
1174 or len(self.required) > self.maxProperties \
1175 or required_is_uninhabited(self)
1177 def updateInternalState(self):
1178 self.compute_actual_min_max_Properties()
1179 self.interval = I.closed(self.minProperties, self.maxProperties)
1180 if len(self.properties) == self.maxProperties \ 1180 ↛ 1183line 1180 didn't jump to line 1183, because the condition on line 1180 was never true
1181 or len(self.patternProperties) == self.maxProperties \
1182 or (len(self.properties) + len(self.patternProperties)) == self.maxProperties:
1183 self.additionalProperties = False
1184 #
1185 # if self.patternProperties != self.kw_defaults["patternProperties"]:
1186 # p = {}
1187 # for k in list(self.patternProperties.keys()):
1188 # v = self.patternProperties.pop(k)
1189 # new_k = utils.regex_unanchor(k)
1190 # p[new_k] = v
1191 # for k in p.keys():
1192 # self.patternProperties[k] = p[k]
1194 def _meet(self, s):
1196 def _meetObject(s1, s2):
1197 if s2.type == "object":
1198 ret = JSONTypeObject({})
1199 ret.required = list(set(s1.required).union(s2.required))
1200 ret.minProperties = max(s1.minProperties, s2.minProperties)
1201 ret.maxProperties = min(s1.maxProperties, s2.maxProperties)
1202 #
1203 if utils.is_bool(s1.additionalProperties) and utils.is_bool(s2.additionalProperties): 1203 ↛ 1204line 1203 didn't jump to line 1204, because the condition on line 1203 was never true
1204 ad = s1.additionalProperties and s2.additionalProperties
1205 elif utils.is_dict(s1.additionalProperties): 1205 ↛ 1208line 1205 didn't jump to line 1208, because the condition on line 1205 was never false
1206 ad = s1.additionalProperties.meet(
1207 s2.additionalProperties)
1208 elif utils.is_dict(s2.additionalProperties):
1209 ad = s2.additionalProperties.meet(
1210 s1.additionalProperties)
1211 ret.additionalProperties = False if is_bot(ad) else ad
1212 #
1213 # For meet of properties and patternProperties,
1214 # no need to check whether a key is valid against patternProperties of the other schema
1215 # or to calculate intersections among patternProperties of both schemas
1216 # cuz the validator takes care of this during validation of actual instances.
1217 # For efficiency, we just include all key in properties and patternProperties of both schemas.
1218 # We only have to handle exactly matching keys in both properties and patternProperties.
1219 #
1220 properties = {}
1221 for k in s1.properties.keys(): 1221 ↛ 1222line 1221 didn't jump to line 1222, because the loop on line 1221 never started
1222 if k in s2.properties.keys():
1223 properties[k] = s1.properties[k].meet(s2.properties[k])
1224 else:
1225 properties[k] = s1.properties[k]
1226 for k in s2.properties.keys(): 1226 ↛ 1227line 1226 didn't jump to line 1227, because the loop on line 1226 never started
1227 if k not in s1.properties.keys():
1228 properties[k] = s2.properties[k]
1229 ret.properties = properties
1230 #
1231 pProperties = {}
1232 for k in s1.patternProperties.keys(): 1232 ↛ 1233line 1232 didn't jump to line 1233, because the loop on line 1232 never started
1233 if k in s2.patternProperties.keys():
1234 pProperties[k] = s1.patternProperties[k].meet(
1235 s2.patternProperties[k])
1236 else:
1237 pProperties[k] = s1.patternProperties[k]
1238 for k in s2.patternProperties.keys(): 1238 ↛ 1239line 1238 didn't jump to line 1239, because the loop on line 1238 never started
1239 if k not in s1.patternProperties.keys():
1240 pProperties[k] = s2.patternProperties[k]
1241 ret.patternProperties = pProperties
1242 #
1243 ret.updateInternalState()
1244 return ret
1245 else:
1246 return JSONbot()
1248 return super().meet_handle_rhs(s, _meetObject)
1250 def _isSubtype(self, s):
1252 def _isObjectSubtype(s1, s2):
1253 ''' The general intuition is that a json object with more keys is more restrictive
1254 than a similar object with fewer keys.
1256 E.g.: if corresponding keys have same schemas, then
1257 {name: {..}, age: {..}} <: {name: {..}}
1258 {name: {..}, age: {..}} />: {name: {..}}
1260 So the subtype checking is divided into two major parts:
1261 I) lhs keys/patterns/additional should be a superset of rhs
1262 II) schemas of comparable keys should have lhs <: rhs
1263 '''
1264 if s2.type != "object":
1265 return False
1266 if s1.hasEnum(): 1266 ↛ 1267line 1266 didn't jump to line 1267, because the condition on line 1266 was never true
1267 return super(JSONTypeObject, s1).subtype_enum(s2)
1268 # Check properties range
1269 is_sub_interval = s1.interval in s2.interval
1270 if not is_sub_interval:
1271 print_db(s1.interval, s1)
1272 print_db(s2.interval, s2)
1273 print_db("__00__")
1274 return False
1275 #
1276 # else:
1277 # # If ranges are ok, check another trivial case of almost identical objects.
1278 # # This is some sort of performance heuristic.
1279 # if set(s1.required).issuperset(s2.required) \
1280 # and s1.properties == s2.properties \
1281 # and s1.patternProperties == s2.patternProperties \
1282 # and (s1.additionalProperties == s2.additionalProperties
1283 # or (utils.is_dict(s1.additionalProperties)
1284 # and s1.additionalProperties.isSubtype(s2.additionalProperties))):
1285 # print_db("__01__")
1286 # return True
1287 # #
1289 def get_schema_for_key(k, s):
1290 ''' Searches for matching key and get the corresponding schema(s).
1291 Returns iterable because if a key matches more than one pattern,
1292 that key schema has to match all corresponding patterns schemas.
1293 '''
1294 if k in s.properties.keys():
1295 return [s.properties[k]]
1296 else:
1297 ret = []
1298 for k_ in s.patternProperties.keys(): 1298 ↛ 1299line 1298 didn't jump to line 1299, because the loop on line 1298 never started
1299 if utils.regex_matches_string(k_, k):
1300 # in case a key has to be checked against patternProperties,
1301 # it has to adhere to all schemas which have pattern matching the key.
1302 ret.append(s.patternProperties[k_])
1303 if ret: 1303 ↛ 1304line 1303 didn't jump to line 1304, because the condition on line 1303 was never true
1304 return ret
1306 return [s.additionalProperties]
1308 # Check that required keys satisfy subtyping.
1309 # lhs required keys should be superset of rhs required keys.
1310 if not set(s1.required).issuperset(s2.required):
1311 print_db("__02__")
1312 return False
1313 # If required keys are properly defined, check their corresponding
1314 # schemas and make sure they are subtypes.
1315 # This is required because you could have a required key which does not
1316 # have an explicit schema defined by the json object.
1318 else:
1319 for k in set(s1.required).intersection(s2.required):
1320 for lhs_ in get_schema_for_key(k, s1):
1321 for rhs_ in get_schema_for_key(k, s2):
1322 if lhs_: 1322 ↛ 1321line 1322 didn't jump to line 1321, because the condition on line 1322 was never false
1323 if rhs_: 1323 ↛ 1329line 1323 didn't jump to line 1329, because the condition on line 1323 was never false
1324 if not lhs_.isSubtype(rhs_):
1325 print_db(k, "LHS", lhs_, "RHS", rhs_)
1326 print_db("!!__03__")
1327 return False
1328 else:
1329 print_db("__04__")
1330 return False
1332 extra_keys_on_rhs = set(s2.properties.keys()).difference(
1333 s1.properties.keys())
1334 for k in extra_keys_on_rhs.copy():
1335 for k_ in s1.patternProperties.keys():
1336 if utils.regex_matches_string(k_, k): 1336 ↛ 1335line 1336 didn't jump to line 1335, because the condition on line 1336 was never false
1337 extra_keys_on_rhs.remove(k)
1338 # if extra_keys_on_rhs:
1339 # if not s1.additionalProperties:
1340 # print_db("?__05__")
1341 # return False
1342 # else:
1343 for k in extra_keys_on_rhs:
1344 if is_bot(s1.additionalProperties):
1345 continue
1346 elif is_top(s1.additionalProperties): 1346 ↛ 1343line 1346 didn't jump to line 1343, because the condition on line 1346 was never false
1347 print_db("__06__")
1348 return False
1349 # for s in get_schema_for_key(k, s1):
1350 # if not is_bot(s):
1351 # continue
1352 # elif is_bot(s2.):
1353 # return False
1354 # print("-->", s)
1355 # if is_top(s) and not is_top(s2.properties[k]) or not s.isSubtype(s2.properties[k]):
1356 # print_db("__06__")
1357 # return False
1359 extra_patterns_on_rhs = set(s2.patternProperties.keys()).difference(
1360 s1.patternProperties.keys())
1361 for k in extra_patterns_on_rhs.copy():
1362 for k_ in s1.patternProperties.keys():
1363 if utils.regex_isSubset(k, k_):
1364 extra_patterns_on_rhs.remove(k)
1365 if extra_patterns_on_rhs:
1366 if not s1.additionalProperties: 1366 ↛ 1367line 1366 didn't jump to line 1367, because the condition on line 1366 was never true
1367 print_db("__07__")
1368 return False
1369 else:
1370 for k in extra_patterns_on_rhs:
1371 if not s1.additionalProperties.isSubtype(s2.patternProperties[k]):
1372 try: # means regex k is infinite
1373 parse(k).cardinality()
1374 except OverflowError:
1375 print_db("__08__")
1376 return False
1377 #
1378 # missing_props_from_lhs = set(
1379 # s2.properties.keys()) - set(s1.properties.keys())
1380 # for k in missing_props_from_lhs:
1381 # for k_ in s1.patternProperties.keys():
1382 # if utils.regex_matches_string(k_, k):
1383 # if not s1.patternProperties[k_].isSubtype(s2.properties[k]):
1384 # return False
1386 # Now, lhs has a patternProperty which is subtype of a property on the rhs.
1387 # Ideally, at this point, I'd like to check that EVERY property matched by
1388 # this pattern also exist on the rhs.
1389 # from greenery.lego import parse
1390 # p = parse(k_)
1391 # try:
1392 # p.cardinality
1394 # first, matching properties should be subtype pairwise
1395 unmatched_lhs_props_keys = set(s1.properties.keys())
1396 for k in s1.properties.keys():
1397 if k in s2.properties.keys():
1398 unmatched_lhs_props_keys.discard(k)
1399 if not s1.properties[k].isSubtype(s2.properties[k]):
1400 return False
1401 # for the remaining keys, make sure they either don't exist
1402 # in rhs or if they, then their schemas should be sub-type
1403 else:
1404 for k_ in s2.patternProperties:
1405 # if utils.regex_isSubset(k, k_):
1406 if utils.regex_matches_string(k_, k): 1406 ↛ 1404line 1406 didn't jump to line 1404, because the condition on line 1406 was never false
1407 unmatched_lhs_props_keys.discard(k)
1408 if not s1.properties[k].isSubtype(s2.patternProperties[k_]): 1408 ↛ 1409line 1408 didn't jump to line 1409, because the condition on line 1408 was never true
1409 return False
1411 # second, matching patternProperties should be subtype pairwise
1412 unmatched_lhs_pProps_keys = set(s1.patternProperties.keys())
1413 for k in s1.patternProperties.keys():
1414 for k_ in s2.patternProperties.keys():
1415 if utils.regex_isSubset(k_, k): 1415 ↛ 1414line 1415 didn't jump to line 1414, because the condition on line 1415 was never false
1416 unmatched_lhs_pProps_keys.discard(k)
1417 if not s1.patternProperties[k].isSubtype(s2.patternProperties[k_]):
1418 return False
1419 # third,
1421 # fourth,
1422 if s2.additionalProperties == True:
1423 return True
1424 elif s2.additionalProperties == False:
1425 if s1.additionalProperties == True: 1425 ↛ 1426line 1425 didn't jump to line 1426, because the condition on line 1425 was never true
1426 return False
1427 elif unmatched_lhs_props_keys or unmatched_lhs_pProps_keys: 1427 ↛ 1428line 1427 didn't jump to line 1428, because the condition on line 1427 was never true
1428 return False
1429 else:
1430 return True
1431 else:
1432 for k in unmatched_lhs_props_keys: 1432 ↛ 1433line 1432 didn't jump to line 1433, because the loop on line 1432 never started
1433 if not s1.properties[k].isSubtype(s2.additionalProperties):
1434 return False
1435 for k in unmatched_lhs_pProps_keys:
1436 if not s1.patternProperties[k].isSubtype(s2.additionalProperties):
1437 return False
1438 if s1.additionalProperties == True: 1438 ↛ 1440line 1438 didn't jump to line 1440, because the condition on line 1438 was never false
1439 return False
1440 elif s1.additionalProperties == False:
1441 return True
1442 else:
1443 return s1.additionalProperties.isSubtype(s2.additionalProperties)
1445 return super().isSubtype_handle_rhs(s, _isObjectSubtype)
1447 @staticmethod
1448 def neg(s):
1449 # for k, default in JSONTypeObject.kw_defaults.items():
1450 # if s.__getattr__(k) != default:
1451 # break
1452 # else:
1453 return None
1456def JSONanyOfFactory(s):
1457 ret = JSONbot()
1458 for i in s.get("anyOf"):
1459 ret = ret.join(i)
1461 return ret
1464class JSONanyOf(JSONschema):
1466 def __init__(self, s):
1467 super().__init__(s)
1468 self.type = "anyOf"
1469 self.anyOf = self.get("anyOf")
1470 self.nonTrivialJoin = False
1472 # def __eq__(self, other):
1473 # ''' This is some sort of hack to compare two anyOf schemas for equality
1474 # instead performing the proper subtype checks. It works for simple cases
1475 # where the schemas in anyOf are dicts with no nested lists/dicts... .
1476 # I think this should be avoided and should not be needed once we are done
1477 # with all cases of join. '''
1478 # if isinstance(other, JSONanyOf):
1479 # return set(tuple(sorted(d.items())) for d in self.anyOf) == set(tuple(sorted(d.items())) for d in other.anyOf)
1480 # else:
1481 # return super().__eq__(other)
1483 def updateInternalState(self):
1484 for d_i in self.anyOf:
1485 if "anyOf" in d_i.keys():
1486 self.anyOf.extend(d_i.get("anyOf"))
1487 self.anyOf.remove(d_i)
1489 def _isUninhabited(self):
1490 return all(is_bot(i) for i in self.anyOf) 1490 ↛ exitline 1490 didn't finish the generator expression on line 1490
1492 def _meet(self, s):
1494 return super().meet_handle_rhs(s, JSONanyOf._meetAnyOf)
1496 @staticmethod
1497 def _meetAnyOf(s1, s2):
1498 anyofs = []
1499 for i in s1.anyOf:
1500 tmp = i.meet(s2)
1501 if not is_bot(tmp):
1502 anyofs.append(tmp)
1504 if len(anyofs) > 1:
1505 return JSONanyOf({"anyOf": anyofs})
1506 elif len(anyofs) == 1:
1507 return anyofs.pop()
1508 else:
1509 return JSONbot()
1511 def _join(self, s):
1512 ret = []
1513 if s.type == "anyOf":
1514 return JSONanyOfFactory({"anyOf": self.anyOf + s.anyOf})
1515 else:
1516 for i in self.anyOf:
1517 if i.type == s.type:
1518 t = i.join(s)
1519 if t.type != "anyOf":
1520 # successful join, add new result and terminate
1521 self.anyOf.remove(i)
1522 self.anyOf.append(t)
1523 break
1524 else:
1525 # loop exited normally without breaking
1526 # so add the single schema manually
1527 self.anyOf.append(s)
1528 return self
1530 def _isSubtype(self, s):
1532 def _isAnyofSubtype(s1, s2):
1533 for s in s1.anyOf:
1534 if not s.isSubtype(s2):
1535 print_db("RHS in anyOf subtype", s2)
1536 return False
1537 return True
1539 return _isAnyofSubtype(self, s)
1542def JSONallOfFactory(s):
1543 ret = JSONtop()
1544 for i in s.get("allOf"):
1545 ret = ret.meet(i)
1547 return ret
1550# def JSONnotFactory(s):
1551# t = s.type
1552# if t in definitions.Jtypes:
1553# anyofs = []
1554# for t_i in definitions.Jtypes - set([t]):
1555# anyofs.append(typeToConstructor.get(t_i)({"type": t_i}))
1556# anyofs.append(negTypeToConstructor.get(t)(s))
1557# return boolToConstructor.get("anyOf")({"anyOf": anyofs})
1558 # return JSONanyOf({"anyOf": anyofs})
1561typeToConstructor = {
1562 "string": JSONTypeString,
1563 "integer": JSONTypeInteger,
1564 "number": JSONTypeNumber,
1565 "boolean": JSONTypeBoolean,
1566 "null": JSONTypeNull,
1567 "array": JSONTypeArray,
1568 "object": JSONTypeObject
1569}
1571boolToConstructor = {
1572 "anyOf": JSONanyOfFactory,
1573 "allOf": JSONallOfFactory
1574}
1577def get_default_types_except(*args):
1578 ret = []
1579 for t in set(typeToConstructor.keys()).difference(args):
1580 ret.append(typeToConstructor[t]({}))
1581 return ret