Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/jsonschema/validators.py: 31%

319 statements  

« prev     ^ index     » next       coverage.py v7.2.7, created at 2023-07-01 06:54 +0000

1""" 

2Creation and extension of validators, with implementations for existing drafts. 

3""" 

4from __future__ import annotations 

5 

6from collections import deque 

7from collections.abc import Mapping, Sequence 

8from functools import lru_cache 

9from operator import methodcaller 

10from urllib.parse import unquote, urldefrag, urljoin, urlsplit 

11from urllib.request import urlopen 

12from warnings import warn 

13import contextlib 

14import json 

15import reprlib 

16import typing 

17import warnings 

18 

19from pyrsistent import m 

20import attr 

21 

22from jsonschema import ( 

23 _format, 

24 _legacy_validators, 

25 _types, 

26 _utils, 

27 _validators, 

28 exceptions, 

29) 

30 

31_UNSET = _utils.Unset() 

32 

33_VALIDATORS: dict[str, typing.Any] = {} 

34_META_SCHEMAS = _utils.URIDict() 

35_VOCABULARIES: list[tuple[str, typing.Any]] = [] 

36 

37 

38def __getattr__(name): 

39 if name == "ErrorTree": 

40 warnings.warn( 

41 "Importing ErrorTree from jsonschema.validators is deprecated. " 

42 "Instead import it from jsonschema.exceptions.", 

43 DeprecationWarning, 

44 stacklevel=2, 

45 ) 

46 from jsonschema.exceptions import ErrorTree 

47 return ErrorTree 

48 elif name == "validators": 

49 warnings.warn( 

50 "Accessing jsonschema.validators.validators is deprecated. " 

51 "Use jsonschema.validators.validator_for with a given schema.", 

52 DeprecationWarning, 

53 stacklevel=2, 

54 ) 

55 return _VALIDATORS 

56 elif name == "meta_schemas": 

57 warnings.warn( 

58 "Accessing jsonschema.validators.meta_schemas is deprecated. " 

59 "Use jsonschema.validators.validator_for with a given schema.", 

60 DeprecationWarning, 

61 stacklevel=2, 

62 ) 

63 return _META_SCHEMAS 

64 raise AttributeError(f"module {__name__} has no attribute {name}") 

65 

66 

67def validates(version): 

68 """ 

69 Register the decorated validator for a ``version`` of the specification. 

70 

71 Registered validators and their meta schemas will be considered when 

72 parsing :kw:`$schema` keywords' URIs. 

73 

74 Arguments: 

75 

76 version (str): 

77 

78 An identifier to use as the version's name 

79 

80 Returns: 

81 

82 collections.abc.Callable: 

83 

84 a class decorator to decorate the validator with the version 

85 """ 

86 

87 def _validates(cls): 

88 _VALIDATORS[version] = cls 

89 meta_schema_id = cls.ID_OF(cls.META_SCHEMA) 

90 _META_SCHEMAS[meta_schema_id] = cls 

91 return cls 

92 return _validates 

93 

94 

95def _id_of(schema): 

96 """ 

97 Return the ID of a schema for recent JSON Schema drafts. 

98 """ 

99 if schema is True or schema is False: 

100 return "" 

101 return schema.get("$id", "") 

102 

103 

104def _store_schema_list(): 

105 if not _VOCABULARIES: 

106 package = _utils.resources.files(__package__) 

107 for version in package.joinpath("schemas", "vocabularies").iterdir(): 

108 for path in version.iterdir(): 

109 vocabulary = json.loads(path.read_text()) 

110 _VOCABULARIES.append((vocabulary["$id"], vocabulary)) 

111 return [ 

112 (id, validator.META_SCHEMA) for id, validator in _META_SCHEMAS.items() 

113 ] + _VOCABULARIES 

114 

115 

116def create( 

117 meta_schema, 

118 validators=(), 

119 version=None, 

120 type_checker=_types.draft202012_type_checker, 

121 format_checker=_format.draft202012_format_checker, 

122 id_of=_id_of, 

123 applicable_validators=methodcaller("items"), 

124): 

125 """ 

126 Create a new validator class. 

127 

128 Arguments: 

129 

130 meta_schema (collections.abc.Mapping): 

131 

132 the meta schema for the new validator class 

133 

134 validators (collections.abc.Mapping): 

135 

136 a mapping from names to callables, where each callable will 

137 validate the schema property with the given name. 

138 

139 Each callable should take 4 arguments: 

140 

141 1. a validator instance, 

142 2. the value of the property being validated within the 

143 instance 

144 3. the instance 

145 4. the schema 

146 

147 version (str): 

148 

149 an identifier for the version that this validator class will 

150 validate. If provided, the returned validator class will 

151 have its ``__name__`` set to include the version, and also 

152 will have `jsonschema.validators.validates` automatically 

153 called for the given version. 

154 

155 type_checker (jsonschema.TypeChecker): 

156 

157 a type checker, used when applying the :kw:`type` keyword. 

158 

159 If unprovided, a `jsonschema.TypeChecker` will be created 

160 with a set of default types typical of JSON Schema drafts. 

161 

162 format_checker (jsonschema.FormatChecker): 

163 

164 a format checker, used when applying the :kw:`format` keyword. 

165 

166 If unprovided, a `jsonschema.FormatChecker` will be created 

167 with a set of default formats typical of JSON Schema drafts. 

168 

169 id_of (collections.abc.Callable): 

170 

171 A function that given a schema, returns its ID. 

172 

173 applicable_validators (collections.abc.Callable): 

174 

175 A function that given a schema, returns the list of 

176 applicable validators (validation keywords and callables) 

177 which will be used to validate the instance. 

178 

179 Returns: 

180 

181 a new `jsonschema.protocols.Validator` class 

182 """ 

183 # preemptively don't shadow the `Validator.format_checker` local 

184 format_checker_arg = format_checker 

185 

186 @attr.s 

187 class Validator: 

188 

189 VALIDATORS = dict(validators) 

190 META_SCHEMA = dict(meta_schema) 

191 TYPE_CHECKER = type_checker 

192 FORMAT_CHECKER = format_checker_arg 

193 ID_OF = staticmethod(id_of) 

194 

195 schema = attr.ib(repr=reprlib.repr) 

196 resolver = attr.ib(default=None, repr=False) 

197 format_checker = attr.ib(default=None) 

198 

199 def __init_subclass__(cls): 

200 warnings.warn( 

201 ( 

202 "Subclassing validator classes is not intended to " 

203 "be part of their public API. A future version " 

204 "will make doing so an error, as the behavior of " 

205 "subclasses isn't guaranteed to stay the same " 

206 "between releases of jsonschema. Instead, prefer " 

207 "composition of validators, wrapping them in an object " 

208 "owned entirely by the downstream library." 

209 ), 

210 DeprecationWarning, 

211 stacklevel=2, 

212 ) 

213 

214 def __attrs_post_init__(self): 

215 if self.resolver is None: 

216 self.resolver = RefResolver.from_schema( 

217 self.schema, 

218 id_of=id_of, 

219 ) 

220 

221 @classmethod 

222 def check_schema(cls, schema, format_checker=_UNSET): 

223 Validator = validator_for(cls.META_SCHEMA, default=cls) 

224 if format_checker is _UNSET: 

225 format_checker = Validator.FORMAT_CHECKER 

226 validator = Validator( 

227 schema=cls.META_SCHEMA, 

228 format_checker=format_checker, 

229 ) 

230 for error in validator.iter_errors(schema): 

231 raise exceptions.SchemaError.create_from(error) 

232 

233 def evolve(self, **changes): 

234 # Essentially reproduces attr.evolve, but may involve instantiating 

235 # a different class than this one. 

236 cls = self.__class__ 

237 

238 schema = changes.setdefault("schema", self.schema) 

239 NewValidator = validator_for(schema, default=cls) 

240 

241 for field in attr.fields(cls): 

242 if not field.init: 

243 continue 

244 attr_name = field.name # To deal with private attributes. 

245 init_name = attr_name if attr_name[0] != "_" else attr_name[1:] 

246 if init_name not in changes: 

247 changes[init_name] = getattr(self, attr_name) 

248 

249 return NewValidator(**changes) 

250 

251 def iter_errors(self, instance, _schema=None): 

252 if _schema is not None: 

253 warnings.warn( 

254 ( 

255 "Passing a schema to Validator.iter_errors " 

256 "is deprecated and will be removed in a future " 

257 "release. Call validator.evolve(schema=new_schema)." 

258 "iter_errors(...) instead." 

259 ), 

260 DeprecationWarning, 

261 stacklevel=2, 

262 ) 

263 else: 

264 _schema = self.schema 

265 

266 if _schema is True: 

267 return 

268 elif _schema is False: 

269 yield exceptions.ValidationError( 

270 f"False schema does not allow {instance!r}", 

271 validator=None, 

272 validator_value=None, 

273 instance=instance, 

274 schema=_schema, 

275 ) 

276 return 

277 

278 scope = id_of(_schema) 

279 if scope: 

280 self.resolver.push_scope(scope) 

281 try: 

282 for k, v in applicable_validators(_schema): 

283 validator = self.VALIDATORS.get(k) 

284 if validator is None: 

285 continue 

286 

287 errors = validator(self, v, instance, _schema) or () 

288 for error in errors: 

289 # set details if not already set by the called fn 

290 error._set( 

291 validator=k, 

292 validator_value=v, 

293 instance=instance, 

294 schema=_schema, 

295 type_checker=self.TYPE_CHECKER, 

296 ) 

297 if k not in {"if", "$ref"}: 

298 error.schema_path.appendleft(k) 

299 yield error 

300 finally: 

301 if scope: 

302 self.resolver.pop_scope() 

303 

304 def descend(self, instance, schema, path=None, schema_path=None): 

305 for error in self.evolve(schema=schema).iter_errors(instance): 

306 if path is not None: 

307 error.path.appendleft(path) 

308 if schema_path is not None: 

309 error.schema_path.appendleft(schema_path) 

310 yield error 

311 

312 def validate(self, *args, **kwargs): 

313 for error in self.iter_errors(*args, **kwargs): 

314 raise error 

315 

316 def is_type(self, instance, type): 

317 try: 

318 return self.TYPE_CHECKER.is_type(instance, type) 

319 except exceptions.UndefinedTypeCheck: 

320 raise exceptions.UnknownType(type, instance, self.schema) 

321 

322 def is_valid(self, instance, _schema=None): 

323 if _schema is not None: 

324 warnings.warn( 

325 ( 

326 "Passing a schema to Validator.is_valid is deprecated " 

327 "and will be removed in a future release. Call " 

328 "validator.evolve(schema=new_schema).is_valid(...) " 

329 "instead." 

330 ), 

331 DeprecationWarning, 

332 stacklevel=2, 

333 ) 

334 self = self.evolve(schema=_schema) 

335 

336 error = next(self.iter_errors(instance), None) 

337 return error is None 

338 

339 if version is not None: 

340 safe = version.title().replace(" ", "").replace("-", "") 

341 Validator.__name__ = Validator.__qualname__ = f"{safe}Validator" 

342 Validator = validates(version)(Validator) 

343 

344 return Validator 

345 

346 

347def extend( 

348 validator, 

349 validators=(), 

350 version=None, 

351 type_checker=None, 

352 format_checker=None, 

353): 

354 """ 

355 Create a new validator class by extending an existing one. 

356 

357 Arguments: 

358 

359 validator (jsonschema.protocols.Validator): 

360 

361 an existing validator class 

362 

363 validators (collections.abc.Mapping): 

364 

365 a mapping of new validator callables to extend with, whose 

366 structure is as in `create`. 

367 

368 .. note:: 

369 

370 Any validator callables with the same name as an 

371 existing one will (silently) replace the old validator 

372 callable entirely, effectively overriding any validation 

373 done in the "parent" validator class. 

374 

375 If you wish to instead extend the behavior of a parent's 

376 validator callable, delegate and call it directly in 

377 the new validator function by retrieving it using 

378 ``OldValidator.VALIDATORS["validation_keyword_name"]``. 

379 

380 version (str): 

381 

382 a version for the new validator class 

383 

384 type_checker (jsonschema.TypeChecker): 

385 

386 a type checker, used when applying the :kw:`type` keyword. 

387 

388 If unprovided, the type checker of the extended 

389 `jsonschema.protocols.Validator` will be carried along. 

390 

391 format_checker (jsonschema.FormatChecker): 

392 

393 a format checker, used when applying the :kw:`format` keyword. 

394 

395 If unprovided, the format checker of the extended 

396 `jsonschema.protocols.Validator` will be carried along. 

397 

398 Returns: 

399 

400 a new `jsonschema.protocols.Validator` class extending the one 

401 provided 

402 

403 .. note:: Meta Schemas 

404 

405 The new validator class will have its parent's meta schema. 

406 

407 If you wish to change or extend the meta schema in the new 

408 validator class, modify ``META_SCHEMA`` directly on the returned 

409 class. Note that no implicit copying is done, so a copy should 

410 likely be made before modifying it, in order to not affect the 

411 old validator. 

412 """ 

413 

414 all_validators = dict(validator.VALIDATORS) 

415 all_validators.update(validators) 

416 

417 if type_checker is None: 

418 type_checker = validator.TYPE_CHECKER 

419 if format_checker is None: 

420 format_checker = validator.FORMAT_CHECKER 

421 return create( 

422 meta_schema=validator.META_SCHEMA, 

423 validators=all_validators, 

424 version=version, 

425 type_checker=type_checker, 

426 format_checker=format_checker, 

427 id_of=validator.ID_OF, 

428 ) 

429 

430 

431Draft3Validator = create( 

432 meta_schema=_utils.load_schema("draft3"), 

433 validators={ 

434 "$ref": _validators.ref, 

435 "additionalItems": _validators.additionalItems, 

436 "additionalProperties": _validators.additionalProperties, 

437 "dependencies": _legacy_validators.dependencies_draft3, 

438 "disallow": _legacy_validators.disallow_draft3, 

439 "divisibleBy": _validators.multipleOf, 

440 "enum": _validators.enum, 

441 "extends": _legacy_validators.extends_draft3, 

442 "format": _validators.format, 

443 "items": _legacy_validators.items_draft3_draft4, 

444 "maxItems": _validators.maxItems, 

445 "maxLength": _validators.maxLength, 

446 "maximum": _legacy_validators.maximum_draft3_draft4, 

447 "minItems": _validators.minItems, 

448 "minLength": _validators.minLength, 

449 "minimum": _legacy_validators.minimum_draft3_draft4, 

450 "pattern": _validators.pattern, 

451 "patternProperties": _validators.patternProperties, 

452 "properties": _legacy_validators.properties_draft3, 

453 "type": _legacy_validators.type_draft3, 

454 "uniqueItems": _validators.uniqueItems, 

455 }, 

456 type_checker=_types.draft3_type_checker, 

457 format_checker=_format.draft3_format_checker, 

458 version="draft3", 

459 id_of=_legacy_validators.id_of_ignore_ref(property="id"), 

460 applicable_validators=_legacy_validators.ignore_ref_siblings, 

461) 

462 

463Draft4Validator = create( 

464 meta_schema=_utils.load_schema("draft4"), 

465 validators={ 

466 "$ref": _validators.ref, 

467 "additionalItems": _validators.additionalItems, 

468 "additionalProperties": _validators.additionalProperties, 

469 "allOf": _validators.allOf, 

470 "anyOf": _validators.anyOf, 

471 "dependencies": _legacy_validators.dependencies_draft4_draft6_draft7, 

472 "enum": _validators.enum, 

473 "format": _validators.format, 

474 "items": _legacy_validators.items_draft3_draft4, 

475 "maxItems": _validators.maxItems, 

476 "maxLength": _validators.maxLength, 

477 "maxProperties": _validators.maxProperties, 

478 "maximum": _legacy_validators.maximum_draft3_draft4, 

479 "minItems": _validators.minItems, 

480 "minLength": _validators.minLength, 

481 "minProperties": _validators.minProperties, 

482 "minimum": _legacy_validators.minimum_draft3_draft4, 

483 "multipleOf": _validators.multipleOf, 

484 "not": _validators.not_, 

485 "oneOf": _validators.oneOf, 

486 "pattern": _validators.pattern, 

487 "patternProperties": _validators.patternProperties, 

488 "properties": _validators.properties, 

489 "required": _validators.required, 

490 "type": _validators.type, 

491 "uniqueItems": _validators.uniqueItems, 

492 }, 

493 type_checker=_types.draft4_type_checker, 

494 format_checker=_format.draft4_format_checker, 

495 version="draft4", 

496 id_of=_legacy_validators.id_of_ignore_ref(property="id"), 

497 applicable_validators=_legacy_validators.ignore_ref_siblings, 

498) 

499 

500Draft6Validator = create( 

501 meta_schema=_utils.load_schema("draft6"), 

502 validators={ 

503 "$ref": _validators.ref, 

504 "additionalItems": _validators.additionalItems, 

505 "additionalProperties": _validators.additionalProperties, 

506 "allOf": _validators.allOf, 

507 "anyOf": _validators.anyOf, 

508 "const": _validators.const, 

509 "contains": _legacy_validators.contains_draft6_draft7, 

510 "dependencies": _legacy_validators.dependencies_draft4_draft6_draft7, 

511 "enum": _validators.enum, 

512 "exclusiveMaximum": _validators.exclusiveMaximum, 

513 "exclusiveMinimum": _validators.exclusiveMinimum, 

514 "format": _validators.format, 

515 "items": _legacy_validators.items_draft6_draft7_draft201909, 

516 "maxItems": _validators.maxItems, 

517 "maxLength": _validators.maxLength, 

518 "maxProperties": _validators.maxProperties, 

519 "maximum": _validators.maximum, 

520 "minItems": _validators.minItems, 

521 "minLength": _validators.minLength, 

522 "minProperties": _validators.minProperties, 

523 "minimum": _validators.minimum, 

524 "multipleOf": _validators.multipleOf, 

525 "not": _validators.not_, 

526 "oneOf": _validators.oneOf, 

527 "pattern": _validators.pattern, 

528 "patternProperties": _validators.patternProperties, 

529 "properties": _validators.properties, 

530 "propertyNames": _validators.propertyNames, 

531 "required": _validators.required, 

532 "type": _validators.type, 

533 "uniqueItems": _validators.uniqueItems, 

534 }, 

535 type_checker=_types.draft6_type_checker, 

536 format_checker=_format.draft6_format_checker, 

537 version="draft6", 

538 id_of=_legacy_validators.id_of_ignore_ref(), 

539 applicable_validators=_legacy_validators.ignore_ref_siblings, 

540) 

541 

542Draft7Validator = create( 

543 meta_schema=_utils.load_schema("draft7"), 

544 validators={ 

545 "$ref": _validators.ref, 

546 "additionalItems": _validators.additionalItems, 

547 "additionalProperties": _validators.additionalProperties, 

548 "allOf": _validators.allOf, 

549 "anyOf": _validators.anyOf, 

550 "const": _validators.const, 

551 "contains": _legacy_validators.contains_draft6_draft7, 

552 "dependencies": _legacy_validators.dependencies_draft4_draft6_draft7, 

553 "enum": _validators.enum, 

554 "exclusiveMaximum": _validators.exclusiveMaximum, 

555 "exclusiveMinimum": _validators.exclusiveMinimum, 

556 "format": _validators.format, 

557 "if": _validators.if_, 

558 "items": _legacy_validators.items_draft6_draft7_draft201909, 

559 "maxItems": _validators.maxItems, 

560 "maxLength": _validators.maxLength, 

561 "maxProperties": _validators.maxProperties, 

562 "maximum": _validators.maximum, 

563 "minItems": _validators.minItems, 

564 "minLength": _validators.minLength, 

565 "minProperties": _validators.minProperties, 

566 "minimum": _validators.minimum, 

567 "multipleOf": _validators.multipleOf, 

568 "not": _validators.not_, 

569 "oneOf": _validators.oneOf, 

570 "pattern": _validators.pattern, 

571 "patternProperties": _validators.patternProperties, 

572 "properties": _validators.properties, 

573 "propertyNames": _validators.propertyNames, 

574 "required": _validators.required, 

575 "type": _validators.type, 

576 "uniqueItems": _validators.uniqueItems, 

577 }, 

578 type_checker=_types.draft7_type_checker, 

579 format_checker=_format.draft7_format_checker, 

580 version="draft7", 

581 id_of=_legacy_validators.id_of_ignore_ref(), 

582 applicable_validators=_legacy_validators.ignore_ref_siblings, 

583) 

584 

585Draft201909Validator = create( 

586 meta_schema=_utils.load_schema("draft2019-09"), 

587 validators={ 

588 "$recursiveRef": _legacy_validators.recursiveRef, 

589 "$ref": _validators.ref, 

590 "additionalItems": _validators.additionalItems, 

591 "additionalProperties": _validators.additionalProperties, 

592 "allOf": _validators.allOf, 

593 "anyOf": _validators.anyOf, 

594 "const": _validators.const, 

595 "contains": _validators.contains, 

596 "dependentRequired": _validators.dependentRequired, 

597 "dependentSchemas": _validators.dependentSchemas, 

598 "enum": _validators.enum, 

599 "exclusiveMaximum": _validators.exclusiveMaximum, 

600 "exclusiveMinimum": _validators.exclusiveMinimum, 

601 "format": _validators.format, 

602 "if": _validators.if_, 

603 "items": _legacy_validators.items_draft6_draft7_draft201909, 

604 "maxItems": _validators.maxItems, 

605 "maxLength": _validators.maxLength, 

606 "maxProperties": _validators.maxProperties, 

607 "maximum": _validators.maximum, 

608 "minItems": _validators.minItems, 

609 "minLength": _validators.minLength, 

610 "minProperties": _validators.minProperties, 

611 "minimum": _validators.minimum, 

612 "multipleOf": _validators.multipleOf, 

613 "not": _validators.not_, 

614 "oneOf": _validators.oneOf, 

615 "pattern": _validators.pattern, 

616 "patternProperties": _validators.patternProperties, 

617 "properties": _validators.properties, 

618 "propertyNames": _validators.propertyNames, 

619 "required": _validators.required, 

620 "type": _validators.type, 

621 "unevaluatedItems": _legacy_validators.unevaluatedItems_draft2019, 

622 "unevaluatedProperties": _validators.unevaluatedProperties, 

623 "uniqueItems": _validators.uniqueItems, 

624 }, 

625 type_checker=_types.draft201909_type_checker, 

626 format_checker=_format.draft201909_format_checker, 

627 version="draft2019-09", 

628) 

629 

630Draft202012Validator = create( 

631 meta_schema=_utils.load_schema("draft2020-12"), 

632 validators={ 

633 "$dynamicRef": _validators.dynamicRef, 

634 "$ref": _validators.ref, 

635 "additionalItems": _validators.additionalItems, 

636 "additionalProperties": _validators.additionalProperties, 

637 "allOf": _validators.allOf, 

638 "anyOf": _validators.anyOf, 

639 "const": _validators.const, 

640 "contains": _validators.contains, 

641 "dependentRequired": _validators.dependentRequired, 

642 "dependentSchemas": _validators.dependentSchemas, 

643 "enum": _validators.enum, 

644 "exclusiveMaximum": _validators.exclusiveMaximum, 

645 "exclusiveMinimum": _validators.exclusiveMinimum, 

646 "format": _validators.format, 

647 "if": _validators.if_, 

648 "items": _validators.items, 

649 "maxItems": _validators.maxItems, 

650 "maxLength": _validators.maxLength, 

651 "maxProperties": _validators.maxProperties, 

652 "maximum": _validators.maximum, 

653 "minItems": _validators.minItems, 

654 "minLength": _validators.minLength, 

655 "minProperties": _validators.minProperties, 

656 "minimum": _validators.minimum, 

657 "multipleOf": _validators.multipleOf, 

658 "not": _validators.not_, 

659 "oneOf": _validators.oneOf, 

660 "pattern": _validators.pattern, 

661 "patternProperties": _validators.patternProperties, 

662 "prefixItems": _validators.prefixItems, 

663 "properties": _validators.properties, 

664 "propertyNames": _validators.propertyNames, 

665 "required": _validators.required, 

666 "type": _validators.type, 

667 "unevaluatedItems": _validators.unevaluatedItems, 

668 "unevaluatedProperties": _validators.unevaluatedProperties, 

669 "uniqueItems": _validators.uniqueItems, 

670 }, 

671 type_checker=_types.draft202012_type_checker, 

672 format_checker=_format.draft202012_format_checker, 

673 version="draft2020-12", 

674) 

675 

676_LATEST_VERSION = Draft202012Validator 

677 

678 

679class RefResolver: 

680 """ 

681 Resolve JSON References. 

682 

683 Arguments: 

684 

685 base_uri (str): 

686 

687 The URI of the referring document 

688 

689 referrer: 

690 

691 The actual referring document 

692 

693 store (dict): 

694 

695 A mapping from URIs to documents to cache 

696 

697 cache_remote (bool): 

698 

699 Whether remote refs should be cached after first resolution 

700 

701 handlers (dict): 

702 

703 A mapping from URI schemes to functions that should be used 

704 to retrieve them 

705 

706 urljoin_cache (:func:`functools.lru_cache`): 

707 

708 A cache that will be used for caching the results of joining 

709 the resolution scope to subscopes. 

710 

711 remote_cache (:func:`functools.lru_cache`): 

712 

713 A cache that will be used for caching the results of 

714 resolved remote URLs. 

715 

716 Attributes: 

717 

718 cache_remote (bool): 

719 

720 Whether remote refs should be cached after first resolution 

721 """ 

722 

723 def __init__( 

724 self, 

725 base_uri, 

726 referrer, 

727 store=m(), 

728 cache_remote=True, 

729 handlers=(), 

730 urljoin_cache=None, 

731 remote_cache=None, 

732 ): 

733 if urljoin_cache is None: 

734 urljoin_cache = lru_cache(1024)(urljoin) 

735 if remote_cache is None: 

736 remote_cache = lru_cache(1024)(self.resolve_from_url) 

737 

738 self.referrer = referrer 

739 self.cache_remote = cache_remote 

740 self.handlers = dict(handlers) 

741 

742 self._scopes_stack = [base_uri] 

743 

744 self.store = _utils.URIDict(_store_schema_list()) 

745 self.store.update(store) 

746 self.store.update( 

747 (schema["$id"], schema) 

748 for schema in store.values() 

749 if isinstance(schema, Mapping) and "$id" in schema 

750 ) 

751 self.store[base_uri] = referrer 

752 

753 self._urljoin_cache = urljoin_cache 

754 self._remote_cache = remote_cache 

755 

756 @classmethod 

757 def from_schema(cls, schema, id_of=_id_of, *args, **kwargs): 

758 """ 

759 Construct a resolver from a JSON schema object. 

760 

761 Arguments: 

762 

763 schema: 

764 

765 the referring schema 

766 

767 Returns: 

768 

769 `RefResolver` 

770 """ 

771 

772 return cls(base_uri=id_of(schema), referrer=schema, *args, **kwargs) # noqa: B026, E501 

773 

774 def push_scope(self, scope): 

775 """ 

776 Enter a given sub-scope. 

777 

778 Treats further dereferences as being performed underneath the 

779 given scope. 

780 """ 

781 self._scopes_stack.append( 

782 self._urljoin_cache(self.resolution_scope, scope), 

783 ) 

784 

785 def pop_scope(self): 

786 """ 

787 Exit the most recent entered scope. 

788 

789 Treats further dereferences as being performed underneath the 

790 original scope. 

791 

792 Don't call this method more times than `push_scope` has been 

793 called. 

794 """ 

795 try: 

796 self._scopes_stack.pop() 

797 except IndexError: 

798 raise exceptions.RefResolutionError( 

799 "Failed to pop the scope from an empty stack. " 

800 "`pop_scope()` should only be called once for every " 

801 "`push_scope()`", 

802 ) 

803 

804 @property 

805 def resolution_scope(self): 

806 """ 

807 Retrieve the current resolution scope. 

808 """ 

809 return self._scopes_stack[-1] 

810 

811 @property 

812 def base_uri(self): 

813 """ 

814 Retrieve the current base URI, not including any fragment. 

815 """ 

816 uri, _ = urldefrag(self.resolution_scope) 

817 return uri 

818 

819 @contextlib.contextmanager 

820 def in_scope(self, scope): 

821 """ 

822 Temporarily enter the given scope for the duration of the context. 

823 

824 .. deprecated:: v4.0.0 

825 """ 

826 warnings.warn( 

827 "jsonschema.RefResolver.in_scope is deprecated and will be " 

828 "removed in a future release.", 

829 DeprecationWarning, 

830 stacklevel=3, 

831 ) 

832 self.push_scope(scope) 

833 try: 

834 yield 

835 finally: 

836 self.pop_scope() 

837 

838 @contextlib.contextmanager 

839 def resolving(self, ref): 

840 """ 

841 Resolve the given ``ref`` and enter its resolution scope. 

842 

843 Exits the scope on exit of this context manager. 

844 

845 Arguments: 

846 

847 ref (str): 

848 

849 The reference to resolve 

850 """ 

851 

852 url, resolved = self.resolve(ref) 

853 self.push_scope(url) 

854 try: 

855 yield resolved 

856 finally: 

857 self.pop_scope() 

858 

859 def _find_in_referrer(self, key): 

860 return self._get_subschemas_cache()[key] 

861 

862 @lru_cache() # noqa: B019 

863 def _get_subschemas_cache(self): 

864 cache = {key: [] for key in _SUBSCHEMAS_KEYWORDS} 

865 for keyword, subschema in _search_schema( 

866 self.referrer, _match_subschema_keywords, 

867 ): 

868 cache[keyword].append(subschema) 

869 return cache 

870 

871 @lru_cache() # noqa: B019 

872 def _find_in_subschemas(self, url): 

873 subschemas = self._get_subschemas_cache()["$id"] 

874 if not subschemas: 

875 return None 

876 uri, fragment = urldefrag(url) 

877 for subschema in subschemas: 

878 target_uri = self._urljoin_cache( 

879 self.resolution_scope, subschema["$id"], 

880 ) 

881 if target_uri.rstrip("/") == uri.rstrip("/"): 

882 if fragment: 

883 subschema = self.resolve_fragment(subschema, fragment) 

884 self.store[url] = subschema 

885 return url, subschema 

886 return None 

887 

888 def resolve(self, ref): 

889 """ 

890 Resolve the given reference. 

891 """ 

892 url = self._urljoin_cache(self.resolution_scope, ref).rstrip("/") 

893 

894 match = self._find_in_subschemas(url) 

895 if match is not None: 

896 return match 

897 

898 return url, self._remote_cache(url) 

899 

900 def resolve_from_url(self, url): 

901 """ 

902 Resolve the given URL. 

903 """ 

904 url, fragment = urldefrag(url) 

905 if not url: 

906 url = self.base_uri 

907 

908 try: 

909 document = self.store[url] 

910 except KeyError: 

911 try: 

912 document = self.resolve_remote(url) 

913 except Exception as exc: 

914 raise exceptions.RefResolutionError(exc) 

915 

916 return self.resolve_fragment(document, fragment) 

917 

918 def resolve_fragment(self, document, fragment): 

919 """ 

920 Resolve a ``fragment`` within the referenced ``document``. 

921 

922 Arguments: 

923 

924 document: 

925 

926 The referent document 

927 

928 fragment (str): 

929 

930 a URI fragment to resolve within it 

931 """ 

932 

933 fragment = fragment.lstrip("/") 

934 

935 if not fragment: 

936 return document 

937 

938 if document is self.referrer: 

939 find = self._find_in_referrer 

940 else: 

941 

942 def find(key): 

943 yield from _search_schema(document, _match_keyword(key)) 

944 

945 for keyword in ["$anchor", "$dynamicAnchor"]: 

946 for subschema in find(keyword): 

947 if fragment == subschema[keyword]: 

948 return subschema 

949 for keyword in ["id", "$id"]: 

950 for subschema in find(keyword): 

951 if "#" + fragment == subschema[keyword]: 

952 return subschema 

953 

954 # Resolve via path 

955 parts = unquote(fragment).split("/") if fragment else [] 

956 for part in parts: 

957 part = part.replace("~1", "/").replace("~0", "~") 

958 

959 if isinstance(document, Sequence): 

960 # Array indexes should be turned into integers 

961 try: 

962 part = int(part) 

963 except ValueError: 

964 pass 

965 try: 

966 document = document[part] 

967 except (TypeError, LookupError): 

968 raise exceptions.RefResolutionError( 

969 f"Unresolvable JSON pointer: {fragment!r}", 

970 ) 

971 

972 return document 

973 

974 def resolve_remote(self, uri): 

975 """ 

976 Resolve a remote ``uri``. 

977 

978 If called directly, does not check the store first, but after 

979 retrieving the document at the specified URI it will be saved in 

980 the store if :attr:`cache_remote` is True. 

981 

982 .. note:: 

983 

984 If the requests_ library is present, ``jsonschema`` will use it to 

985 request the remote ``uri``, so that the correct encoding is 

986 detected and used. 

987 

988 If it isn't, or if the scheme of the ``uri`` is not ``http`` or 

989 ``https``, UTF-8 is assumed. 

990 

991 Arguments: 

992 

993 uri (str): 

994 

995 The URI to resolve 

996 

997 Returns: 

998 

999 The retrieved document 

1000 

1001 .. _requests: https://pypi.org/project/requests/ 

1002 """ 

1003 try: 

1004 import requests 

1005 except ImportError: 

1006 requests = None 

1007 

1008 scheme = urlsplit(uri).scheme 

1009 

1010 if scheme in self.handlers: 

1011 result = self.handlers[scheme](uri) 

1012 elif scheme in ["http", "https"] and requests: 

1013 # Requests has support for detecting the correct encoding of 

1014 # json over http 

1015 result = requests.get(uri).json() 

1016 else: 

1017 # Otherwise, pass off to urllib and assume utf-8 

1018 with urlopen(uri) as url: 

1019 result = json.loads(url.read().decode("utf-8")) 

1020 

1021 if self.cache_remote: 

1022 self.store[uri] = result 

1023 return result 

1024 

1025 

1026_SUBSCHEMAS_KEYWORDS = ("$id", "id", "$anchor", "$dynamicAnchor") 

1027 

1028 

1029def _match_keyword(keyword): 

1030 

1031 def matcher(value): 

1032 if keyword in value: 

1033 yield value 

1034 

1035 return matcher 

1036 

1037 

1038def _match_subschema_keywords(value): 

1039 for keyword in _SUBSCHEMAS_KEYWORDS: 

1040 if keyword in value: 

1041 yield keyword, value 

1042 

1043 

1044def _search_schema(schema, matcher): 

1045 """Breadth-first search routine.""" 

1046 values = deque([schema]) 

1047 while values: 

1048 value = values.pop() 

1049 if not isinstance(value, dict): 

1050 continue 

1051 yield from matcher(value) 

1052 values.extendleft(value.values()) 

1053 

1054 

1055def validate(instance, schema, cls=None, *args, **kwargs): 

1056 """ 

1057 Validate an instance under the given schema. 

1058 

1059 >>> validate([2, 3, 4], {"maxItems": 2}) 

1060 Traceback (most recent call last): 

1061 ... 

1062 ValidationError: [2, 3, 4] is too long 

1063 

1064 :func:`~jsonschema.validators.validate` will first verify that the 

1065 provided schema is itself valid, since not doing so can lead to less 

1066 obvious error messages and fail in less obvious or consistent ways. 

1067 

1068 If you know you have a valid schema already, especially 

1069 if you intend to validate multiple instances with 

1070 the same schema, you likely would prefer using the 

1071 `jsonschema.protocols.Validator.validate` method directly on a 

1072 specific validator (e.g. ``Draft20212Validator.validate``). 

1073 

1074 

1075 Arguments: 

1076 

1077 instance: 

1078 

1079 The instance to validate 

1080 

1081 schema: 

1082 

1083 The schema to validate with 

1084 

1085 cls (jsonschema.protocols.Validator): 

1086 

1087 The class that will be used to validate the instance. 

1088 

1089 If the ``cls`` argument is not provided, two things will happen 

1090 in accordance with the specification. First, if the schema has a 

1091 :kw:`$schema` keyword containing a known meta-schema [#]_ then the 

1092 proper validator will be used. The specification recommends that 

1093 all schemas contain :kw:`$schema` properties for this reason. If no 

1094 :kw:`$schema` property is found, the default validator class is the 

1095 latest released draft. 

1096 

1097 Any other provided positional and keyword arguments will be passed 

1098 on when instantiating the ``cls``. 

1099 

1100 Raises: 

1101 

1102 `jsonschema.exceptions.ValidationError`: 

1103 

1104 if the instance is invalid 

1105 

1106 `jsonschema.exceptions.SchemaError`: 

1107 

1108 if the schema itself is invalid 

1109 

1110 .. rubric:: Footnotes 

1111 .. [#] known by a validator registered with 

1112 `jsonschema.validators.validates` 

1113 """ 

1114 if cls is None: 

1115 cls = validator_for(schema) 

1116 

1117 cls.check_schema(schema) 

1118 validator = cls(schema, *args, **kwargs) 

1119 error = exceptions.best_match(validator.iter_errors(instance)) 

1120 if error is not None: 

1121 raise error 

1122 

1123 

1124def validator_for(schema, default=_UNSET): 

1125 """ 

1126 Retrieve the validator class appropriate for validating the given schema. 

1127 

1128 Uses the :kw:`$schema` keyword that should be present in the given 

1129 schema to look up the appropriate validator class. 

1130 

1131 Arguments: 

1132 

1133 schema (collections.abc.Mapping or bool): 

1134 

1135 the schema to look at 

1136 

1137 default: 

1138 

1139 the default to return if the appropriate validator class 

1140 cannot be determined. 

1141 

1142 If unprovided, the default is to return the latest supported 

1143 draft. 

1144 """ 

1145 

1146 DefaultValidator = _LATEST_VERSION if default is _UNSET else default 

1147 

1148 if schema is True or schema is False or "$schema" not in schema: 

1149 return DefaultValidator 

1150 if schema["$schema"] not in _META_SCHEMAS: 

1151 if default is _UNSET: 

1152 warn( 

1153 ( 

1154 "The metaschema specified by $schema was not found. " 

1155 "Using the latest draft to validate, but this will raise " 

1156 "an error in the future." 

1157 ), 

1158 DeprecationWarning, 

1159 stacklevel=2, 

1160 ) 

1161 return _META_SCHEMAS.get(schema["$schema"], DefaultValidator)