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

380 statements  

« prev     ^ index     » next       coverage.py v7.3.1, created at 2023-09-25 06:44 +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 Iterable, 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 warnings 

17 

18from attrs import define, field, fields 

19from jsonschema_specifications import REGISTRY as SPECIFICATIONS 

20from rpds import HashTrieMap 

21import referencing.exceptions 

22import referencing.jsonschema 

23 

24from jsonschema import ( 

25 _format, 

26 _keywords, 

27 _legacy_keywords, 

28 _types, 

29 _typing, 

30 _utils, 

31 exceptions, 

32) 

33from jsonschema.protocols import Validator 

34 

35_UNSET = _utils.Unset() 

36 

37_VALIDATORS: dict[str, Validator] = {} 

38_META_SCHEMAS = _utils.URIDict() 

39 

40 

41def __getattr__(name): 

42 if name == "ErrorTree": 

43 warnings.warn( 

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

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

46 DeprecationWarning, 

47 stacklevel=2, 

48 ) 

49 from jsonschema.exceptions import ErrorTree 

50 return ErrorTree 

51 elif name == "validators": 

52 warnings.warn( 

53 "Accessing jsonschema.validators.validators is deprecated. " 

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

55 DeprecationWarning, 

56 stacklevel=2, 

57 ) 

58 return _VALIDATORS 

59 elif name == "meta_schemas": 

60 warnings.warn( 

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

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

63 DeprecationWarning, 

64 stacklevel=2, 

65 ) 

66 return _META_SCHEMAS 

67 elif name == "RefResolver": 

68 warnings.warn( 

69 _RefResolver._DEPRECATION_MESSAGE, 

70 DeprecationWarning, 

71 stacklevel=2, 

72 ) 

73 return _RefResolver 

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

75 

76 

77def validates(version): 

78 """ 

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

80 

81 Registered validators and their meta schemas will be considered when 

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

83 

84 Arguments: 

85 

86 version (str): 

87 

88 An identifier to use as the version's name 

89 

90 Returns: 

91 

92 collections.abc.Callable: 

93 

94 a class decorator to decorate the validator with the version 

95 """ 

96 

97 def _validates(cls): 

98 _VALIDATORS[version] = cls 

99 meta_schema_id = cls.ID_OF(cls.META_SCHEMA) 

100 _META_SCHEMAS[meta_schema_id] = cls 

101 return cls 

102 return _validates 

103 

104 

105def _warn_for_remote_retrieve(uri: str): 

106 from urllib.request import Request, urlopen 

107 headers = {"User-Agent": "python-jsonschema (deprecated $ref resolution)"} 

108 request = Request(uri, headers=headers) 

109 with urlopen(request) as response: 

110 warnings.warn( 

111 "Automatically retrieving remote references can be a security " 

112 "vulnerability and is discouraged by the JSON Schema " 

113 "specifications. Relying on this behavior is deprecated " 

114 "and will shortly become an error. If you are sure you want to " 

115 "remotely retrieve your reference and that it is safe to do so, " 

116 "you can find instructions for doing so via referencing.Registry " 

117 "in the referencing documentation " 

118 "(https://referencing.readthedocs.org).", 

119 DeprecationWarning, 

120 stacklevel=9, # Ha ha ha ha magic numbers :/ 

121 ) 

122 return referencing.Resource.from_contents( 

123 json.load(response), 

124 default_specification=referencing.jsonschema.DRAFT202012, 

125 ) 

126 

127 

128_REMOTE_WARNING_REGISTRY = SPECIFICATIONS.combine( 

129 referencing.Registry(retrieve=_warn_for_remote_retrieve), # type: ignore[call-arg] # noqa: E501 

130) 

131 

132 

133def create( 

134 meta_schema: referencing.jsonschema.ObjectSchema, 

135 validators: ( 

136 Mapping[str, _typing.SchemaKeywordValidator] 

137 | Iterable[tuple[str, _typing.SchemaKeywordValidator]] 

138 ) = (), 

139 version: str | None = None, 

140 type_checker: _types.TypeChecker = _types.draft202012_type_checker, 

141 format_checker: _format.FormatChecker = _format.draft202012_format_checker, 

142 id_of: _typing.id_of = referencing.jsonschema.DRAFT202012.id_of, 

143 applicable_validators: _typing.ApplicableValidators = methodcaller( 

144 "items", 

145 ), 

146): 

147 """ 

148 Create a new validator class. 

149 

150 Arguments: 

151 

152 meta_schema: 

153 

154 the meta schema for the new validator class 

155 

156 validators: 

157 

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

159 validate the schema property with the given name. 

160 

161 Each callable should take 4 arguments: 

162 

163 1. a validator instance, 

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

165 instance 

166 3. the instance 

167 4. the schema 

168 

169 version: 

170 

171 an identifier for the version that this validator class will 

172 validate. If provided, the returned validator class will 

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

174 will have `jsonschema.validators.validates` automatically 

175 called for the given version. 

176 

177 type_checker: 

178 

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

180 

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

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

183 

184 format_checker: 

185 

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

187 

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

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

190 

191 id_of: 

192 

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

194 

195 applicable_validators: 

196 

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

198 applicable schema keywords and associated values 

199 which will be used to validate the instance. 

200 This is mostly used to support pre-draft 7 versions of JSON Schema 

201 which specified behavior around ignoring keywords if they were 

202 siblings of a ``$ref`` keyword. If you're not attempting to 

203 implement similar behavior, you can typically ignore this argument 

204 and leave it at its default. 

205 

206 Returns: 

207 

208 a new `jsonschema.protocols.Validator` class 

209 """ 

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

211 format_checker_arg = format_checker 

212 

213 specification = referencing.jsonschema.specification_with( 

214 dialect_id=id_of(meta_schema) or "urn:unknown-dialect", 

215 default=referencing.Specification.OPAQUE, 

216 ) 

217 

218 @define 

219 class Validator: 

220 

221 VALIDATORS = dict(validators) # noqa: RUF012 

222 META_SCHEMA = dict(meta_schema) # noqa: RUF012 

223 TYPE_CHECKER = type_checker 

224 FORMAT_CHECKER = format_checker_arg 

225 ID_OF = staticmethod(id_of) 

226 

227 _APPLICABLE_VALIDATORS = applicable_validators 

228 

229 schema: referencing.jsonschema.Schema = field(repr=reprlib.repr) 

230 _ref_resolver = field(default=None, repr=False, alias="resolver") 

231 format_checker: _format.FormatChecker | None = field(default=None) 

232 # TODO: include new meta-schemas added at runtime 

233 _registry: referencing.jsonschema.SchemaRegistry = field( 

234 default=_REMOTE_WARNING_REGISTRY, 

235 kw_only=True, 

236 repr=False, 

237 ) 

238 _resolver = field( 

239 alias="_resolver", 

240 default=None, 

241 kw_only=True, 

242 repr=False, 

243 ) 

244 

245 def __init_subclass__(cls): 

246 warnings.warn( 

247 ( 

248 "Subclassing validator classes is not intended to " 

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

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

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

252 "between releases of jsonschema. Instead, prefer " 

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

254 "owned entirely by the downstream library." 

255 ), 

256 DeprecationWarning, 

257 stacklevel=2, 

258 ) 

259 

260 def evolve(self, **changes): 

261 cls = self.__class__ 

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

263 NewValidator = validator_for(schema, default=cls) 

264 

265 for field in fields(cls): # noqa: F402 

266 if not field.init: 

267 continue 

268 attr_name = field.name 

269 init_name = field.alias 

270 if init_name not in changes: 

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

272 

273 return NewValidator(**changes) 

274 

275 cls.evolve = evolve 

276 

277 def __attrs_post_init__(self): 

278 if self._resolver is None: 

279 registry = self._registry 

280 if registry is not _REMOTE_WARNING_REGISTRY: 

281 registry = SPECIFICATIONS.combine(registry) 

282 resource = specification.create_resource(self.schema) 

283 self._resolver = registry.resolver_with_root(resource) 

284 

285 # REMOVEME: Legacy ref resolution state management. 

286 push_scope = getattr(self._ref_resolver, "push_scope", None) 

287 if push_scope is not None: 

288 id = id_of(self.schema) 

289 if id is not None: 

290 push_scope(id) 

291 

292 @classmethod 

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

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

295 if format_checker is _UNSET: 

296 format_checker = Validator.FORMAT_CHECKER 

297 validator = Validator( 

298 schema=cls.META_SCHEMA, 

299 format_checker=format_checker, 

300 ) 

301 for error in validator.iter_errors(schema): 

302 raise exceptions.SchemaError.create_from(error) 

303 

304 @property 

305 def resolver(self): 

306 warnings.warn( 

307 ( 

308 f"Accessing {self.__class__.__name__}.resolver is " 

309 "deprecated as of v4.18.0, in favor of the " 

310 "https://github.com/python-jsonschema/referencing " 

311 "library, which provides more compliant referencing " 

312 "behavior as well as more flexible APIs for " 

313 "customization." 

314 ), 

315 DeprecationWarning, 

316 stacklevel=2, 

317 ) 

318 if self._ref_resolver is None: 

319 self._ref_resolver = _RefResolver.from_schema( 

320 self.schema, 

321 id_of=id_of, 

322 ) 

323 return self._ref_resolver 

324 

325 def evolve(self, **changes): 

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

327 NewValidator = validator_for(schema, default=self.__class__) 

328 

329 for (attr_name, init_name) in evolve_fields: 

330 if init_name not in changes: 

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

332 

333 return NewValidator(**changes) 

334 

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

336 if _schema is not None: 

337 warnings.warn( 

338 ( 

339 "Passing a schema to Validator.iter_errors " 

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

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

342 "iter_errors(...) instead." 

343 ), 

344 DeprecationWarning, 

345 stacklevel=2, 

346 ) 

347 else: 

348 _schema = self.schema 

349 

350 if _schema is True: 

351 return 

352 elif _schema is False: 

353 yield exceptions.ValidationError( 

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

355 validator=None, 

356 validator_value=None, 

357 instance=instance, 

358 schema=_schema, 

359 ) 

360 return 

361 

362 for k, v in applicable_validators(_schema): 

363 validator = self.VALIDATORS.get(k) 

364 if validator is None: 

365 continue 

366 

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

368 for error in errors: 

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

370 error._set( 

371 validator=k, 

372 validator_value=v, 

373 instance=instance, 

374 schema=_schema, 

375 type_checker=self.TYPE_CHECKER, 

376 ) 

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

378 error.schema_path.appendleft(k) 

379 yield error 

380 

381 def descend( 

382 self, 

383 instance, 

384 schema, 

385 path=None, 

386 schema_path=None, 

387 resolver=None, 

388 ): 

389 if schema is True: 

390 return 

391 elif schema is False: 

392 yield exceptions.ValidationError( 

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

394 validator=None, 

395 validator_value=None, 

396 instance=instance, 

397 schema=schema, 

398 ) 

399 return 

400 

401 if self._ref_resolver is not None: 

402 evolved = self.evolve(schema=schema) 

403 else: 

404 if resolver is None: 

405 resolver = self._resolver.in_subresource( 

406 specification.create_resource(schema), 

407 ) 

408 evolved = self.evolve(schema=schema, _resolver=resolver) 

409 

410 for k, v in applicable_validators(schema): 

411 validator = evolved.VALIDATORS.get(k) 

412 if validator is None: 

413 continue 

414 

415 errors = validator(evolved, v, instance, schema) or () 

416 for error in errors: 

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

418 error._set( 

419 validator=k, 

420 validator_value=v, 

421 instance=instance, 

422 schema=schema, 

423 type_checker=evolved.TYPE_CHECKER, 

424 ) 

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

426 error.schema_path.appendleft(k) 

427 if path is not None: 

428 error.path.appendleft(path) 

429 if schema_path is not None: 

430 error.schema_path.appendleft(schema_path) 

431 yield error 

432 

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

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

435 raise error 

436 

437 def is_type(self, instance, type): 

438 try: 

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

440 except exceptions.UndefinedTypeCheck: 

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

442 

443 def _validate_reference(self, ref, instance): 

444 if self._ref_resolver is None: 

445 try: 

446 resolved = self._resolver.lookup(ref) 

447 except referencing.exceptions.Unresolvable as err: 

448 raise exceptions._WrappedReferencingError(err) 

449 

450 return self.descend( 

451 instance, 

452 resolved.contents, 

453 resolver=resolved.resolver, 

454 ) 

455 else: 

456 resolve = getattr(self._ref_resolver, "resolve", None) 

457 if resolve is None: 

458 with self._ref_resolver.resolving(ref) as resolved: 

459 return self.descend(instance, resolved) 

460 else: 

461 scope, resolved = resolve(ref) 

462 self._ref_resolver.push_scope(scope) 

463 

464 try: 

465 return list(self.descend(instance, resolved)) 

466 finally: 

467 self._ref_resolver.pop_scope() 

468 

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

470 if _schema is not None: 

471 warnings.warn( 

472 ( 

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

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

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

476 "instead." 

477 ), 

478 DeprecationWarning, 

479 stacklevel=2, 

480 ) 

481 self = self.evolve(schema=_schema) 

482 

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

484 return error is None 

485 

486 evolve_fields = [ 

487 (field.name, field.alias) 

488 for field in fields(Validator) 

489 if field.init 

490 ] 

491 

492 if version is not None: 

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

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

495 Validator = validates(version)(Validator) # type: ignore[misc] 

496 

497 return Validator 

498 

499 

500def extend( 

501 validator, 

502 validators=(), 

503 version=None, 

504 type_checker=None, 

505 format_checker=None, 

506): 

507 """ 

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

509 

510 Arguments: 

511 

512 validator (jsonschema.protocols.Validator): 

513 

514 an existing validator class 

515 

516 validators (collections.abc.Mapping): 

517 

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

519 structure is as in `create`. 

520 

521 .. note:: 

522 

523 Any validator callables with the same name as an 

524 existing one will (silently) replace the old validator 

525 callable entirely, effectively overriding any validation 

526 done in the "parent" validator class. 

527 

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

529 validator callable, delegate and call it directly in 

530 the new validator function by retrieving it using 

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

532 

533 version (str): 

534 

535 a version for the new validator class 

536 

537 type_checker (jsonschema.TypeChecker): 

538 

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

540 

541 If unprovided, the type checker of the extended 

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

543 

544 format_checker (jsonschema.FormatChecker): 

545 

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

547 

548 If unprovided, the format checker of the extended 

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

550 

551 Returns: 

552 

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

554 provided 

555 

556 .. note:: Meta Schemas 

557 

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

559 

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

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

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

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

564 old validator. 

565 """ 

566 all_validators = dict(validator.VALIDATORS) 

567 all_validators.update(validators) 

568 

569 if type_checker is None: 

570 type_checker = validator.TYPE_CHECKER 

571 if format_checker is None: 

572 format_checker = validator.FORMAT_CHECKER 

573 return create( 

574 meta_schema=validator.META_SCHEMA, 

575 validators=all_validators, 

576 version=version, 

577 type_checker=type_checker, 

578 format_checker=format_checker, 

579 id_of=validator.ID_OF, 

580 applicable_validators=validator._APPLICABLE_VALIDATORS, 

581 ) 

582 

583 

584Draft3Validator = create( 

585 meta_schema=SPECIFICATIONS.contents( 

586 "http://json-schema.org/draft-03/schema#", 

587 ), 

588 validators={ 

589 "$ref": _keywords.ref, 

590 "additionalItems": _keywords.additionalItems, 

591 "additionalProperties": _keywords.additionalProperties, 

592 "dependencies": _legacy_keywords.dependencies_draft3, 

593 "disallow": _legacy_keywords.disallow_draft3, 

594 "divisibleBy": _keywords.multipleOf, 

595 "enum": _keywords.enum, 

596 "extends": _legacy_keywords.extends_draft3, 

597 "format": _keywords.format, 

598 "items": _legacy_keywords.items_draft3_draft4, 

599 "maxItems": _keywords.maxItems, 

600 "maxLength": _keywords.maxLength, 

601 "maximum": _legacy_keywords.maximum_draft3_draft4, 

602 "minItems": _keywords.minItems, 

603 "minLength": _keywords.minLength, 

604 "minimum": _legacy_keywords.minimum_draft3_draft4, 

605 "pattern": _keywords.pattern, 

606 "patternProperties": _keywords.patternProperties, 

607 "properties": _legacy_keywords.properties_draft3, 

608 "type": _legacy_keywords.type_draft3, 

609 "uniqueItems": _keywords.uniqueItems, 

610 }, 

611 type_checker=_types.draft3_type_checker, 

612 format_checker=_format.draft3_format_checker, 

613 version="draft3", 

614 id_of=referencing.jsonschema.DRAFT3.id_of, 

615 applicable_validators=_legacy_keywords.ignore_ref_siblings, 

616) 

617 

618Draft4Validator = create( 

619 meta_schema=SPECIFICATIONS.contents( 

620 "http://json-schema.org/draft-04/schema#", 

621 ), 

622 validators={ 

623 "$ref": _keywords.ref, 

624 "additionalItems": _keywords.additionalItems, 

625 "additionalProperties": _keywords.additionalProperties, 

626 "allOf": _keywords.allOf, 

627 "anyOf": _keywords.anyOf, 

628 "dependencies": _legacy_keywords.dependencies_draft4_draft6_draft7, 

629 "enum": _keywords.enum, 

630 "format": _keywords.format, 

631 "items": _legacy_keywords.items_draft3_draft4, 

632 "maxItems": _keywords.maxItems, 

633 "maxLength": _keywords.maxLength, 

634 "maxProperties": _keywords.maxProperties, 

635 "maximum": _legacy_keywords.maximum_draft3_draft4, 

636 "minItems": _keywords.minItems, 

637 "minLength": _keywords.minLength, 

638 "minProperties": _keywords.minProperties, 

639 "minimum": _legacy_keywords.minimum_draft3_draft4, 

640 "multipleOf": _keywords.multipleOf, 

641 "not": _keywords.not_, 

642 "oneOf": _keywords.oneOf, 

643 "pattern": _keywords.pattern, 

644 "patternProperties": _keywords.patternProperties, 

645 "properties": _keywords.properties, 

646 "required": _keywords.required, 

647 "type": _keywords.type, 

648 "uniqueItems": _keywords.uniqueItems, 

649 }, 

650 type_checker=_types.draft4_type_checker, 

651 format_checker=_format.draft4_format_checker, 

652 version="draft4", 

653 id_of=referencing.jsonschema.DRAFT4.id_of, 

654 applicable_validators=_legacy_keywords.ignore_ref_siblings, 

655) 

656 

657Draft6Validator = create( 

658 meta_schema=SPECIFICATIONS.contents( 

659 "http://json-schema.org/draft-06/schema#", 

660 ), 

661 validators={ 

662 "$ref": _keywords.ref, 

663 "additionalItems": _keywords.additionalItems, 

664 "additionalProperties": _keywords.additionalProperties, 

665 "allOf": _keywords.allOf, 

666 "anyOf": _keywords.anyOf, 

667 "const": _keywords.const, 

668 "contains": _legacy_keywords.contains_draft6_draft7, 

669 "dependencies": _legacy_keywords.dependencies_draft4_draft6_draft7, 

670 "enum": _keywords.enum, 

671 "exclusiveMaximum": _keywords.exclusiveMaximum, 

672 "exclusiveMinimum": _keywords.exclusiveMinimum, 

673 "format": _keywords.format, 

674 "items": _legacy_keywords.items_draft6_draft7_draft201909, 

675 "maxItems": _keywords.maxItems, 

676 "maxLength": _keywords.maxLength, 

677 "maxProperties": _keywords.maxProperties, 

678 "maximum": _keywords.maximum, 

679 "minItems": _keywords.minItems, 

680 "minLength": _keywords.minLength, 

681 "minProperties": _keywords.minProperties, 

682 "minimum": _keywords.minimum, 

683 "multipleOf": _keywords.multipleOf, 

684 "not": _keywords.not_, 

685 "oneOf": _keywords.oneOf, 

686 "pattern": _keywords.pattern, 

687 "patternProperties": _keywords.patternProperties, 

688 "properties": _keywords.properties, 

689 "propertyNames": _keywords.propertyNames, 

690 "required": _keywords.required, 

691 "type": _keywords.type, 

692 "uniqueItems": _keywords.uniqueItems, 

693 }, 

694 type_checker=_types.draft6_type_checker, 

695 format_checker=_format.draft6_format_checker, 

696 version="draft6", 

697 id_of=referencing.jsonschema.DRAFT6.id_of, 

698 applicable_validators=_legacy_keywords.ignore_ref_siblings, 

699) 

700 

701Draft7Validator = create( 

702 meta_schema=SPECIFICATIONS.contents( 

703 "http://json-schema.org/draft-07/schema#", 

704 ), 

705 validators={ 

706 "$ref": _keywords.ref, 

707 "additionalItems": _keywords.additionalItems, 

708 "additionalProperties": _keywords.additionalProperties, 

709 "allOf": _keywords.allOf, 

710 "anyOf": _keywords.anyOf, 

711 "const": _keywords.const, 

712 "contains": _legacy_keywords.contains_draft6_draft7, 

713 "dependencies": _legacy_keywords.dependencies_draft4_draft6_draft7, 

714 "enum": _keywords.enum, 

715 "exclusiveMaximum": _keywords.exclusiveMaximum, 

716 "exclusiveMinimum": _keywords.exclusiveMinimum, 

717 "format": _keywords.format, 

718 "if": _keywords.if_, 

719 "items": _legacy_keywords.items_draft6_draft7_draft201909, 

720 "maxItems": _keywords.maxItems, 

721 "maxLength": _keywords.maxLength, 

722 "maxProperties": _keywords.maxProperties, 

723 "maximum": _keywords.maximum, 

724 "minItems": _keywords.minItems, 

725 "minLength": _keywords.minLength, 

726 "minProperties": _keywords.minProperties, 

727 "minimum": _keywords.minimum, 

728 "multipleOf": _keywords.multipleOf, 

729 "not": _keywords.not_, 

730 "oneOf": _keywords.oneOf, 

731 "pattern": _keywords.pattern, 

732 "patternProperties": _keywords.patternProperties, 

733 "properties": _keywords.properties, 

734 "propertyNames": _keywords.propertyNames, 

735 "required": _keywords.required, 

736 "type": _keywords.type, 

737 "uniqueItems": _keywords.uniqueItems, 

738 }, 

739 type_checker=_types.draft7_type_checker, 

740 format_checker=_format.draft7_format_checker, 

741 version="draft7", 

742 id_of=referencing.jsonschema.DRAFT7.id_of, 

743 applicable_validators=_legacy_keywords.ignore_ref_siblings, 

744) 

745 

746Draft201909Validator = create( 

747 meta_schema=SPECIFICATIONS.contents( 

748 "https://json-schema.org/draft/2019-09/schema", 

749 ), 

750 validators={ 

751 "$recursiveRef": _legacy_keywords.recursiveRef, 

752 "$ref": _keywords.ref, 

753 "additionalItems": _keywords.additionalItems, 

754 "additionalProperties": _keywords.additionalProperties, 

755 "allOf": _keywords.allOf, 

756 "anyOf": _keywords.anyOf, 

757 "const": _keywords.const, 

758 "contains": _keywords.contains, 

759 "dependentRequired": _keywords.dependentRequired, 

760 "dependentSchemas": _keywords.dependentSchemas, 

761 "enum": _keywords.enum, 

762 "exclusiveMaximum": _keywords.exclusiveMaximum, 

763 "exclusiveMinimum": _keywords.exclusiveMinimum, 

764 "format": _keywords.format, 

765 "if": _keywords.if_, 

766 "items": _legacy_keywords.items_draft6_draft7_draft201909, 

767 "maxItems": _keywords.maxItems, 

768 "maxLength": _keywords.maxLength, 

769 "maxProperties": _keywords.maxProperties, 

770 "maximum": _keywords.maximum, 

771 "minItems": _keywords.minItems, 

772 "minLength": _keywords.minLength, 

773 "minProperties": _keywords.minProperties, 

774 "minimum": _keywords.minimum, 

775 "multipleOf": _keywords.multipleOf, 

776 "not": _keywords.not_, 

777 "oneOf": _keywords.oneOf, 

778 "pattern": _keywords.pattern, 

779 "patternProperties": _keywords.patternProperties, 

780 "properties": _keywords.properties, 

781 "propertyNames": _keywords.propertyNames, 

782 "required": _keywords.required, 

783 "type": _keywords.type, 

784 "unevaluatedItems": _legacy_keywords.unevaluatedItems_draft2019, 

785 "unevaluatedProperties": _keywords.unevaluatedProperties, 

786 "uniqueItems": _keywords.uniqueItems, 

787 }, 

788 type_checker=_types.draft201909_type_checker, 

789 format_checker=_format.draft201909_format_checker, 

790 version="draft2019-09", 

791) 

792 

793Draft202012Validator = create( 

794 meta_schema=SPECIFICATIONS.contents( 

795 "https://json-schema.org/draft/2020-12/schema", 

796 ), 

797 validators={ 

798 "$dynamicRef": _keywords.dynamicRef, 

799 "$ref": _keywords.ref, 

800 "additionalItems": _keywords.additionalItems, 

801 "additionalProperties": _keywords.additionalProperties, 

802 "allOf": _keywords.allOf, 

803 "anyOf": _keywords.anyOf, 

804 "const": _keywords.const, 

805 "contains": _keywords.contains, 

806 "dependentRequired": _keywords.dependentRequired, 

807 "dependentSchemas": _keywords.dependentSchemas, 

808 "enum": _keywords.enum, 

809 "exclusiveMaximum": _keywords.exclusiveMaximum, 

810 "exclusiveMinimum": _keywords.exclusiveMinimum, 

811 "format": _keywords.format, 

812 "if": _keywords.if_, 

813 "items": _keywords.items, 

814 "maxItems": _keywords.maxItems, 

815 "maxLength": _keywords.maxLength, 

816 "maxProperties": _keywords.maxProperties, 

817 "maximum": _keywords.maximum, 

818 "minItems": _keywords.minItems, 

819 "minLength": _keywords.minLength, 

820 "minProperties": _keywords.minProperties, 

821 "minimum": _keywords.minimum, 

822 "multipleOf": _keywords.multipleOf, 

823 "not": _keywords.not_, 

824 "oneOf": _keywords.oneOf, 

825 "pattern": _keywords.pattern, 

826 "patternProperties": _keywords.patternProperties, 

827 "prefixItems": _keywords.prefixItems, 

828 "properties": _keywords.properties, 

829 "propertyNames": _keywords.propertyNames, 

830 "required": _keywords.required, 

831 "type": _keywords.type, 

832 "unevaluatedItems": _keywords.unevaluatedItems, 

833 "unevaluatedProperties": _keywords.unevaluatedProperties, 

834 "uniqueItems": _keywords.uniqueItems, 

835 }, 

836 type_checker=_types.draft202012_type_checker, 

837 format_checker=_format.draft202012_format_checker, 

838 version="draft2020-12", 

839) 

840 

841_LATEST_VERSION = Draft202012Validator 

842 

843 

844class _RefResolver: 

845 """ 

846 Resolve JSON References. 

847 

848 Arguments: 

849 

850 base_uri (str): 

851 

852 The URI of the referring document 

853 

854 referrer: 

855 

856 The actual referring document 

857 

858 store (dict): 

859 

860 A mapping from URIs to documents to cache 

861 

862 cache_remote (bool): 

863 

864 Whether remote refs should be cached after first resolution 

865 

866 handlers (dict): 

867 

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

869 to retrieve them 

870 

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

872 

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

874 the resolution scope to subscopes. 

875 

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

877 

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

879 resolved remote URLs. 

880 

881 Attributes: 

882 

883 cache_remote (bool): 

884 

885 Whether remote refs should be cached after first resolution 

886 

887 .. deprecated:: v4.18.0 

888 

889 ``RefResolver`` has been deprecated in favor of `referencing`. 

890 """ 

891 

892 _DEPRECATION_MESSAGE = ( 

893 "jsonschema.RefResolver is deprecated as of v4.18.0, in favor of the " 

894 "https://github.com/python-jsonschema/referencing library, which " 

895 "provides more compliant referencing behavior as well as more " 

896 "flexible APIs for customization. A future release will remove " 

897 "RefResolver. Please file a feature request (on referencing) if you " 

898 "are missing an API for the kind of customization you need." 

899 ) 

900 

901 def __init__( 

902 self, 

903 base_uri, 

904 referrer, 

905 store=HashTrieMap(), 

906 cache_remote=True, 

907 handlers=(), 

908 urljoin_cache=None, 

909 remote_cache=None, 

910 ): 

911 if urljoin_cache is None: 

912 urljoin_cache = lru_cache(1024)(urljoin) 

913 if remote_cache is None: 

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

915 

916 self.referrer = referrer 

917 self.cache_remote = cache_remote 

918 self.handlers = dict(handlers) 

919 

920 self._scopes_stack = [base_uri] 

921 

922 self.store = _utils.URIDict( 

923 (uri, each.contents) for uri, each in SPECIFICATIONS.items() 

924 ) 

925 self.store.update( 

926 (id, each.META_SCHEMA) for id, each in _META_SCHEMAS.items() 

927 ) 

928 self.store.update(store) 

929 self.store.update( 

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

931 for schema in store.values() 

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

933 ) 

934 self.store[base_uri] = referrer 

935 

936 self._urljoin_cache = urljoin_cache 

937 self._remote_cache = remote_cache 

938 

939 @classmethod 

940 def from_schema( # noqa: D417 

941 cls, 

942 schema, 

943 id_of=referencing.jsonschema.DRAFT202012.id_of, 

944 *args, 

945 **kwargs, 

946 ): 

947 """ 

948 Construct a resolver from a JSON schema object. 

949 

950 Arguments: 

951 

952 schema: 

953 

954 the referring schema 

955 

956 Returns: 

957 

958 `_RefResolver` 

959 """ 

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

961 

962 def push_scope(self, scope): 

963 """ 

964 Enter a given sub-scope. 

965 

966 Treats further dereferences as being performed underneath the 

967 given scope. 

968 """ 

969 self._scopes_stack.append( 

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

971 ) 

972 

973 def pop_scope(self): 

974 """ 

975 Exit the most recent entered scope. 

976 

977 Treats further dereferences as being performed underneath the 

978 original scope. 

979 

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

981 called. 

982 """ 

983 try: 

984 self._scopes_stack.pop() 

985 except IndexError: 

986 raise exceptions._RefResolutionError( 

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

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

989 "`push_scope()`", 

990 ) 

991 

992 @property 

993 def resolution_scope(self): 

994 """ 

995 Retrieve the current resolution scope. 

996 """ 

997 return self._scopes_stack[-1] 

998 

999 @property 

1000 def base_uri(self): 

1001 """ 

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

1003 """ 

1004 uri, _ = urldefrag(self.resolution_scope) 

1005 return uri 

1006 

1007 @contextlib.contextmanager 

1008 def in_scope(self, scope): 

1009 """ 

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

1011 

1012 .. deprecated:: v4.0.0 

1013 """ 

1014 warnings.warn( 

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

1016 "removed in a future release.", 

1017 DeprecationWarning, 

1018 stacklevel=3, 

1019 ) 

1020 self.push_scope(scope) 

1021 try: 

1022 yield 

1023 finally: 

1024 self.pop_scope() 

1025 

1026 @contextlib.contextmanager 

1027 def resolving(self, ref): 

1028 """ 

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

1030 

1031 Exits the scope on exit of this context manager. 

1032 

1033 Arguments: 

1034 

1035 ref (str): 

1036 

1037 The reference to resolve 

1038 """ 

1039 url, resolved = self.resolve(ref) 

1040 self.push_scope(url) 

1041 try: 

1042 yield resolved 

1043 finally: 

1044 self.pop_scope() 

1045 

1046 def _find_in_referrer(self, key): 

1047 return self._get_subschemas_cache()[key] 

1048 

1049 @lru_cache # noqa: B019 

1050 def _get_subschemas_cache(self): 

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

1052 for keyword, subschema in _search_schema( 

1053 self.referrer, _match_subschema_keywords, 

1054 ): 

1055 cache[keyword].append(subschema) 

1056 return cache 

1057 

1058 @lru_cache # noqa: B019 

1059 def _find_in_subschemas(self, url): 

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

1061 if not subschemas: 

1062 return None 

1063 uri, fragment = urldefrag(url) 

1064 for subschema in subschemas: 

1065 id = subschema["$id"] 

1066 if not isinstance(id, str): 

1067 continue 

1068 target_uri = self._urljoin_cache(self.resolution_scope, id) 

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

1070 if fragment: 

1071 subschema = self.resolve_fragment(subschema, fragment) 

1072 self.store[url] = subschema 

1073 return url, subschema 

1074 return None 

1075 

1076 def resolve(self, ref): 

1077 """ 

1078 Resolve the given reference. 

1079 """ 

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

1081 

1082 match = self._find_in_subschemas(url) 

1083 if match is not None: 

1084 return match 

1085 

1086 return url, self._remote_cache(url) 

1087 

1088 def resolve_from_url(self, url): 

1089 """ 

1090 Resolve the given URL. 

1091 """ 

1092 url, fragment = urldefrag(url) 

1093 if not url: 

1094 url = self.base_uri 

1095 

1096 try: 

1097 document = self.store[url] 

1098 except KeyError: 

1099 try: 

1100 document = self.resolve_remote(url) 

1101 except Exception as exc: 

1102 raise exceptions._RefResolutionError(exc) 

1103 

1104 return self.resolve_fragment(document, fragment) 

1105 

1106 def resolve_fragment(self, document, fragment): 

1107 """ 

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

1109 

1110 Arguments: 

1111 

1112 document: 

1113 

1114 The referent document 

1115 

1116 fragment (str): 

1117 

1118 a URI fragment to resolve within it 

1119 """ 

1120 fragment = fragment.lstrip("/") 

1121 

1122 if not fragment: 

1123 return document 

1124 

1125 if document is self.referrer: 

1126 find = self._find_in_referrer 

1127 else: 

1128 

1129 def find(key): 

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

1131 

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

1133 for subschema in find(keyword): 

1134 if fragment == subschema[keyword]: 

1135 return subschema 

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

1137 for subschema in find(keyword): 

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

1139 return subschema 

1140 

1141 # Resolve via path 

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

1143 for part in parts: 

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

1145 

1146 if isinstance(document, Sequence): 

1147 try: # noqa: SIM105 

1148 part = int(part) 

1149 except ValueError: 

1150 pass 

1151 try: 

1152 document = document[part] 

1153 except (TypeError, LookupError): 

1154 raise exceptions._RefResolutionError( 

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

1156 ) 

1157 

1158 return document 

1159 

1160 def resolve_remote(self, uri): 

1161 """ 

1162 Resolve a remote ``uri``. 

1163 

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

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

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

1167 

1168 .. note:: 

1169 

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

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

1172 detected and used. 

1173 

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

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

1176 

1177 Arguments: 

1178 

1179 uri (str): 

1180 

1181 The URI to resolve 

1182 

1183 Returns: 

1184 

1185 The retrieved document 

1186 

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

1188 """ 

1189 try: 

1190 import requests 

1191 except ImportError: 

1192 requests = None 

1193 

1194 scheme = urlsplit(uri).scheme 

1195 

1196 if scheme in self.handlers: 

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

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

1199 # Requests has support for detecting the correct encoding of 

1200 # json over http 

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

1202 else: 

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

1204 with urlopen(uri) as url: 

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

1206 

1207 if self.cache_remote: 

1208 self.store[uri] = result 

1209 return result 

1210 

1211 

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

1213 

1214 

1215def _match_keyword(keyword): 

1216 

1217 def matcher(value): 

1218 if keyword in value: 

1219 yield value 

1220 

1221 return matcher 

1222 

1223 

1224def _match_subschema_keywords(value): 

1225 for keyword in _SUBSCHEMAS_KEYWORDS: 

1226 if keyword in value: 

1227 yield keyword, value 

1228 

1229 

1230def _search_schema(schema, matcher): 

1231 """Breadth-first search routine.""" 

1232 values = deque([schema]) 

1233 while values: 

1234 value = values.pop() 

1235 if not isinstance(value, dict): 

1236 continue 

1237 yield from matcher(value) 

1238 values.extendleft(value.values()) 

1239 

1240 

1241def validate(instance, schema, cls=None, *args, **kwargs): # noqa: D417 

1242 """ 

1243 Validate an instance under the given schema. 

1244 

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

1246 Traceback (most recent call last): 

1247 ... 

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

1249 

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

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

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

1253 

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

1255 if you intend to validate multiple instances with 

1256 the same schema, you likely would prefer using the 

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

1258 specific validator (e.g. ``Draft202012Validator.validate``). 

1259 

1260 

1261 Arguments: 

1262 

1263 instance: 

1264 

1265 The instance to validate 

1266 

1267 schema: 

1268 

1269 The schema to validate with 

1270 

1271 cls (jsonschema.protocols.Validator): 

1272 

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

1274 

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

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

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

1278 proper validator will be used. The specification recommends that 

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

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

1281 latest released draft. 

1282 

1283 Any other provided positional and keyword arguments will be passed 

1284 on when instantiating the ``cls``. 

1285 

1286 Raises: 

1287 

1288 `jsonschema.exceptions.ValidationError`: 

1289 

1290 if the instance is invalid 

1291 

1292 `jsonschema.exceptions.SchemaError`: 

1293 

1294 if the schema itself is invalid 

1295 

1296 .. rubric:: Footnotes 

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

1298 `jsonschema.validators.validates` 

1299 """ 

1300 if cls is None: 

1301 cls = validator_for(schema) 

1302 

1303 cls.check_schema(schema) 

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

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

1306 if error is not None: 

1307 raise error 

1308 

1309 

1310def validator_for(schema, default=_UNSET): 

1311 """ 

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

1313 

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

1315 schema to look up the appropriate validator class. 

1316 

1317 Arguments: 

1318 

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

1320 

1321 the schema to look at 

1322 

1323 default: 

1324 

1325 the default to return if the appropriate validator class 

1326 cannot be determined. 

1327 

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

1329 draft. 

1330 

1331 Examples: 

1332 

1333 The :kw:`$schema` JSON Schema keyword will control which validator 

1334 class is returned: 

1335 

1336 >>> schema = { 

1337 ... "$schema": "https://json-schema.org/draft/2020-12/schema", 

1338 ... "type": "integer", 

1339 ... } 

1340 >>> jsonschema.validators.validator_for(schema) 

1341 <class 'jsonschema.validators.Draft202012Validator'> 

1342 

1343 

1344 Here, a draft 7 schema instead will return the draft 7 validator: 

1345 

1346 >>> schema = { 

1347 ... "$schema": "http://json-schema.org/draft-07/schema#", 

1348 ... "type": "integer", 

1349 ... } 

1350 >>> jsonschema.validators.validator_for(schema) 

1351 <class 'jsonschema.validators.Draft7Validator'> 

1352 

1353 

1354 Schemas with no ``$schema`` keyword will fallback to the default 

1355 argument: 

1356 

1357 >>> schema = {"type": "integer"} 

1358 >>> jsonschema.validators.validator_for( 

1359 ... schema, default=Draft7Validator, 

1360 ... ) 

1361 <class 'jsonschema.validators.Draft7Validator'> 

1362 

1363 or if none is provided, to the latest version supported. 

1364 Always including the keyword when authoring schemas is highly 

1365 recommended. 

1366 

1367 """ 

1368 DefaultValidator = _LATEST_VERSION if default is _UNSET else default 

1369 

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

1371 return DefaultValidator 

1372 if schema["$schema"] not in _META_SCHEMAS and default is _UNSET: 

1373 warn( 

1374 ( 

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

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

1377 "an error in the future." 

1378 ), 

1379 DeprecationWarning, 

1380 stacklevel=2, 

1381 ) 

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