Hide keyboard shortcuts

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''' 

5 

6import copy 

7import json 

8import math 

9import sys 

10 

11import portion as I 

12from greenery.lego import parse 

13 

14import jsonsubschema.config as config 

15 

16import jsonsubschema._constants as definitions 

17import jsonsubschema._utils as utils 

18from jsonsubschema._utils import print_db 

19 

20 

21class UninhabitedMeta(type): 

22 

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 

29 

30 

31class JSONschema(dict, metaclass=UninhabitedMeta): 

32 

33 def __init__(self, *args, **kwargs): 

34 

35 super().__init__(*args, **kwargs) 

36 

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) 

42 

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"] 

47 

48 def updateInternalState(self): 

49 pass 

50 

51 def isBoolean(self): 

52 return self.keys() & definitions.Jconnectors 

53 

54 def hasEnum(self): 

55 return "enum" in self.keys() or hasattr(self, "enum") 

56 

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 

67 

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 

93 

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) 

101 

102 def meet_handle_rhs(self, s, meet_cb): 

103 

104 if s.type == "anyOf": 

105 return JSONanyOf._meetAnyOf(s, self) 

106 

107 else: 

108 return meet_cb(self, s) 

109 

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) 

116 

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 

140 

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 

148 

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) 

160 

161 def isSubtype_nonTrivial(self, s): 

162 return self._isSubtype_nonTrivial(s) 

163 

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 

175 

176 def isSubtype_handle_rhs(self, s, isSubtype_cb): 

177 

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) 

184 

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) 

195 

196 

197class JSONtop(JSONschema): 

198 def __init__(self): 

199 super().__init__({}) 

200 self.type = "top" 

201 

202 def _isUninhabited(self): 

203 return False 

204 

205 def _meet(self, s): 

206 return s 

207 

208 def _join(self, s): 

209 return self 

210 

211 def _isSubtype(self, s): 

212 

213 def _isTopSubtype(s1, s2): 

214 if is_top(s2): 

215 return True 

216 return False 

217 

218 super().isSubtype_handle_rhs(s, _isTopSubtype) 

219 

220 def __eq__(self, s): 

221 if is_top(s): 

222 return True 

223 else: 

224 return False 

225 

226 def __repr__(self): 

227 return "JSON_TOP" 

228 

229 def __bool__(self): 

230 return True 

231 

232 

233def is_top(obj): 

234 return obj == True or obj == {} or isinstance(obj, JSONtop) 

235 

236 

237class JSONbot(JSONschema): 

238 def __init__(self): 

239 super().__init__({"not": {}}) 

240 self.type = "bot" 

241 

242 def _isUninhabited(self): 

243 return True 

244 

245 def _meet(self, s): 

246 return self 

247 

248 def _join(self, s): 

249 return s 

250 

251 def _isSubtype(self, s): 

252 

253 def _isBotSubtype(s1, s2): 

254 if is_bot(s2): 

255 return True 

256 return False 

257 

258 super().isSubtype_handle_rhs(s, _isBotSubtype) 

259 

260 def __eq__(self, s): 

261 if is_bot(s): 

262 return True 

263 else: 

264 return False 

265 

266 def __repr__(self): 

267 return "JSON_BOT" 

268 

269 def __bool__(self): 

270 return False 

271 

272 

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()) 

278 

279 

280class JSONTypeString(JSONschema): 

281 

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 = "" 

295 

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 

301 

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) 

309 

310 def _meet(self, s): 

311 

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() 

329 

330 return super().meet_handle_rhs(s, _meetString) 

331 

332 def _join(self, s): 

333 

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]}) 

359 

360 return _joinString(self, s) 

361 

362 def _isSubtype(self, s): 

363 

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) 

381 

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) 

388 

389 if utils.regex_isSubset(pattern1, pattern2): 

390 return True 

391 else: 

392 return False 

393 else: 

394 return False 

395 

396 return super().isSubtype_handle_rhs(s, _isStringSubtype) 

397 

398 @staticmethod 

399 def neg(s): 

400 negated_strings = [] 

401 non_string = boolToConstructor.get("anyOf")( 

402 {"anyOf": get_default_types_except("string")}) 

403 

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) + "$"})) 

417 

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) 

424 

425 

426class JSONTypeNumeric(JSONschema): 

427 

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) 

435 

436 def _isUninhabited(self): 

437 return self.interval.empty \ 

438 or utils.is_num(self.multipleOf) and self.multipleOf > self.maximum 

439 

440 def updateInternalState(self): 

441 self.build_interval_draft4() 

442 

443 def _meet(self, s): 

444 

445 def _meetNumeric(s1, s2): 

446 if s1.type in definitions.Jnumeric and s2.type in definitions.Jnumeric: 

447 ret = {} 

448 

449 mn = max(s1.minimum, s2.minimum) 

450 if utils.is_num(mn): 

451 ret["minimum"] = mn 

452 

453 mx = min(s1.maximum, s2.maximum) 

454 if utils.is_num(mx): 

455 ret["maximum"] = mx 

456 

457 mulOf = utils.lcm(s1.multipleOf, s2.multipleOf) 

458 if mulOf: 

459 ret["multipleOf"] = mulOf 

460 

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) 

465 

466 else: 

467 return JSONbot() 

468 

469 return super().meet_handle_rhs(s, _meetNumeric) 

470 

471 def _join(self, s): 

472 # join integer with number 

473 return JSONanyOf({"anyOf": [self, s]}) 

474 

475 

476class JSONTypeInteger(JSONTypeNumeric): 

477 

478 def __init__(self, s): 

479 super().__init__(s) 

480 self.type = self["type"] = "integer" 

481 

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. 

489 

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) 

497 

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) 

505 

506 self.minimum, self.maximum = utils.get_new_min_max_with_mulof( 

507 self.minimum, self.maximum, self.multipleOf) 

508 

509 self.interval = I.closed(self.minimum, self.maximum) 

510 

511 def _join(self, s): 

512 

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 

536 

537 print_db("NonTrivial is not set") 

538 return JSONanyOf({"anyOf": [s1, s2]}) 

539 

540 return _joinInteger(self, s) 

541 

542 def _isSubtype(self, s): 

543 

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 

564 

565 return super().isSubtype_handle_rhs(s, _isIntegerSubtype) 

566 

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) 

579 

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) 

588 

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 

600 

601 return True 

602 

603 @staticmethod 

604 def neg(s): 

605 negated_ints = [] 

606 non_ints = boolToConstructor.get("anyOf")( 

607 {"anyOf": get_default_types_except("number", "integer")}) 

608 

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. 

622 

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) 

629 

630 

631class JSONTypeNumber(JSONTypeNumeric): 

632 

633 def __init__(self, s): 

634 super().__init__(s) 

635 self.type = self["type"] = "number" 

636 

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) 

646 

647 def _join(self, s): 

648 

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]}) 

667 

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]}) 

675 

676 return _joinNumber(self, s) 

677 

678 def _isSubtype(self, s): 

679 

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 

708 

709 return super().isSubtype_handle_rhs(s, _isNumberSubtype) 

710 

711 @staticmethod 

712 def neg(s): 

713 negated_numbers = [] 

714 non_numbers = boolToConstructor.get("anyOf")( 

715 {"anyOf": get_default_types_except("number", "integer")}) 

716 

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. 

732 

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) 

739 

740 

741class JSONTypeBoolean(JSONschema): 

742 

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] 

749 

750 def _isUninhabited(self): 

751 return False 

752 

753 def _meet(self, s): 

754 

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() 

771 

772 return super().meet_handle_rhs(s, _meetBoolean) 

773 

774 def _isSubtype(self, s): 

775 

776 def _isBooleanSubtype(self, s2): 

777 if s2.type == "boolean": 

778 return True 

779 else: 

780 return False 

781 

782 return super().isSubtype_handle_rhs(s, _isBooleanSubtype) 

783 

784 @staticmethod 

785 def neg(s): 

786 negated_boolean = [] 

787 non_boolean = boolToConstructor.get("anyOf")( 

788 {"anyOf": get_default_types_except("boolean")}) 

789 

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]]})) 

796 

797 return non_boolean 

798 

799 

800class JSONTypeNull(JSONschema): 

801 

802 def __init__(self, s): 

803 super().__init__(s) 

804 self.type = self["type"] = "null" 

805 

806 def _isUninhabited(self): 

807 return False 

808 

809 def _meet(self, s): 

810 

811 def _meetNull(s1, s2): 

812 

813 if s2.type == "null": 

814 return s1 

815 else: 

816 return JSONbot() 

817 

818 return super().meet_handle_rhs(s, _meetNull) 

819 

820 def _isSubtype(self, s): 

821 

822 def _isNullSubtype(self, s2): 

823 if s2.type == "null": 

824 return True 

825 else: 

826 return False 

827 

828 return super().isSubtype_handle_rhs(s, _isNullSubtype) 

829 

830 @staticmethod 

831 def neg(s): 

832 return boolToConstructor.get("anyOf")( 

833 {"anyOf": get_default_types_except("null")}) 

834 

835 

836class JSONTypeArray(JSONschema): 

837 

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) 

846 

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 

852 

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) 

858 

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 

864 

865 def _meet(self, s): 

866 

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 

879 

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'" 

883 

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 

891 

892 ret.items_ = itms 

893 

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 

903 

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

905 

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_) 

908 

909 elif utils.is_list(s2.items_): 

910 ret = meet_arrayItems_dict_list(s1, s2, ret) 

911 

912 elif utils.is_list(s1.items_): 

913 

914 if utils.is_dict(s2.items_): 

915 ret = meet_arrayItems_dict_list(s2, s1, ret) 

916 

917 elif utils.is_list(s2.items_): 

918 self_len = len(s1.items_) 

919 s_len = len(s2.items_) 

920 

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 

931 

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) 

955 

956 ret.additionalItems = ad 

957 ret.items_ = itms 

958 return ret 

959 

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) 

972 

973 ret.additionalItems = ad 

974 ret.items_ = itms 

975 

976 elif self_len > s_len: 

977 ret = meet_array_longlist_shorterlist(s1, s2, ret) 

978 

979 elif self_len < s_len: 

980 ret = meet_array_longlist_shorterlist(s2, s1, ret) 

981 ret.updateInternalState() 

982 return ret 

983 

984 else: 

985 return JSONbot() 

986 

987 return super().meet_handle_rhs(s, _meetArray) 

988 

989 def _isSubtype(self, s): 

990 

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) 

1115 

1116 return super().isSubtype_handle_rhs(s, _isArraySubtype) 

1117 

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 

1125 

1126 

1127class JSONTypeObject(JSONschema): 

1128 

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 

1141 

1142 def compute_actual_min_max_Properties(self): 

1143 

1144 new_min = max(self.minProperties, len(self.required)) 

1145 if new_min != self.minProperties: 

1146 self.minProperties = new_min 

1147 

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 

1152 

1153 def _isUninhabited(self): 

1154 

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 

1160 

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 

1170 

1171 return False 

1172 

1173 return self.minProperties > self.maxProperties \ 

1174 or len(self.required) > self.maxProperties \ 

1175 or required_is_uninhabited(self) 

1176 

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] 

1193 

1194 def _meet(self, s): 

1195 

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() 

1247 

1248 return super().meet_handle_rhs(s, _meetObject) 

1249 

1250 def _isSubtype(self, s): 

1251 

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.  

1255 

1256 E.g.: if corresponding keys have same schemas, then  

1257 {name: {..}, age: {..}} <: {name: {..}} 

1258 {name: {..}, age: {..}} />: {name: {..}} 

1259 

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 # # 

1288 

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 

1305 

1306 return [s.additionalProperties] 

1307 

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. 

1317 

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 

1331 

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 

1358 

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 

1385 

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 

1393 

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 

1410 

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, 

1420 

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) 

1444 

1445 return super().isSubtype_handle_rhs(s, _isObjectSubtype) 

1446 

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 

1454 

1455 

1456def JSONanyOfFactory(s): 

1457 ret = JSONbot() 

1458 for i in s.get("anyOf"): 

1459 ret = ret.join(i) 

1460 

1461 return ret 

1462 

1463 

1464class JSONanyOf(JSONschema): 

1465 

1466 def __init__(self, s): 

1467 super().__init__(s) 

1468 self.type = "anyOf" 

1469 self.anyOf = self.get("anyOf") 

1470 self.nonTrivialJoin = False 

1471 

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) 

1482 

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) 

1488 

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

1491 

1492 def _meet(self, s): 

1493 

1494 return super().meet_handle_rhs(s, JSONanyOf._meetAnyOf) 

1495 

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) 

1503 

1504 if len(anyofs) > 1: 

1505 return JSONanyOf({"anyOf": anyofs}) 

1506 elif len(anyofs) == 1: 

1507 return anyofs.pop() 

1508 else: 

1509 return JSONbot() 

1510 

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 

1529 

1530 def _isSubtype(self, s): 

1531 

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 

1538 

1539 return _isAnyofSubtype(self, s) 

1540 

1541 

1542def JSONallOfFactory(s): 

1543 ret = JSONtop() 

1544 for i in s.get("allOf"): 

1545 ret = ret.meet(i) 

1546 

1547 return ret 

1548 

1549 

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}) 

1559 

1560 

1561typeToConstructor = { 

1562 "string": JSONTypeString, 

1563 "integer": JSONTypeInteger, 

1564 "number": JSONTypeNumber, 

1565 "boolean": JSONTypeBoolean, 

1566 "null": JSONTypeNull, 

1567 "array": JSONTypeArray, 

1568 "object": JSONTypeObject 

1569} 

1570 

1571boolToConstructor = { 

1572 "anyOf": JSONanyOfFactory, 

1573 "allOf": JSONallOfFactory 

1574} 

1575 

1576 

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