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

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

385 statements  

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 typing import TYPE_CHECKING 

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

12from urllib.request import urlopen 

13from warnings import warn 

14import contextlib 

15import json 

16import reprlib 

17import warnings 

18 

19from attrs import define, field, fields 

20from jsonschema_specifications import REGISTRY as SPECIFICATIONS 

21from rpds import HashTrieMap 

22import referencing.exceptions 

23import referencing.jsonschema 

24 

25from jsonschema import ( 

26 _format, 

27 _keywords, 

28 _legacy_keywords, 

29 _types, 

30 _typing, 

31 _utils, 

32 exceptions, 

33) 

34 

35if TYPE_CHECKING: 

36 from jsonschema.protocols import Validator 

37 

38_UNSET = _utils.Unset() 

39 

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

41_META_SCHEMAS = _utils.URIDict() 

42 

43 

44def __getattr__(name): 

45 if name == "ErrorTree": 

46 warnings.warn( 

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

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

49 DeprecationWarning, 

50 stacklevel=2, 

51 ) 

52 from jsonschema.exceptions import ErrorTree 

53 return ErrorTree 

54 elif name == "validators": 

55 warnings.warn( 

56 "Accessing jsonschema.validators.validators is deprecated. " 

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

58 DeprecationWarning, 

59 stacklevel=2, 

60 ) 

61 return _VALIDATORS 

62 elif name == "meta_schemas": 

63 warnings.warn( 

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

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

66 DeprecationWarning, 

67 stacklevel=2, 

68 ) 

69 return _META_SCHEMAS 

70 elif name == "RefResolver": 

71 warnings.warn( 

72 _RefResolver._DEPRECATION_MESSAGE, 

73 DeprecationWarning, 

74 stacklevel=2, 

75 ) 

76 return _RefResolver 

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

78 

79 

80def validates(version): 

81 """ 

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

83 

84 Registered validators and their meta schemas will be considered when 

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

86 

87 Arguments: 

88 

89 version (str): 

90 

91 An identifier to use as the version's name 

92 

93 Returns: 

94 

95 collections.abc.Callable: 

96 

97 a class decorator to decorate the validator with the version 

98 

99 """ 

100 

101 def _validates(cls): 

102 _VALIDATORS[version] = cls 

103 meta_schema_id = cls.ID_OF(cls.META_SCHEMA) 

104 _META_SCHEMAS[meta_schema_id] = cls 

105 return cls 

106 return _validates 

107 

108 

109def _warn_for_remote_retrieve(uri: str): 

110 from urllib.request import Request, urlopen 

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

112 request = Request(uri, headers=headers) # noqa: S310 

113 with urlopen(request) as response: # noqa: S310 

114 warnings.warn( 

115 "Automatically retrieving remote references can be a security " 

116 "vulnerability and is discouraged by the JSON Schema " 

117 "specifications. Relying on this behavior is deprecated " 

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

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

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

121 "in the referencing documentation " 

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

123 DeprecationWarning, 

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

125 ) 

126 return referencing.Resource.from_contents( 

127 json.load(response), 

128 default_specification=referencing.jsonschema.DRAFT202012, 

129 ) 

130 

131 

132_REMOTE_WARNING_REGISTRY = SPECIFICATIONS.combine( 

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

134) 

135 

136 

137def create( 

138 meta_schema: referencing.jsonschema.ObjectSchema, 

139 validators: ( 

140 Mapping[str, _typing.SchemaKeywordValidator] 

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

142 ) = (), 

143 version: str | None = None, 

144 type_checker: _types.TypeChecker = _types.draft202012_type_checker, 

145 format_checker: _format.FormatChecker = _format.draft202012_format_checker, 

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

147 applicable_validators: _typing.ApplicableValidators = methodcaller( 

148 "items", 

149 ), 

150): 

151 """ 

152 Create a new validator class. 

153 

154 Arguments: 

155 

156 meta_schema: 

157 

158 the meta schema for the new validator class 

159 

160 validators: 

161 

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

163 validate the schema property with the given name. 

164 

165 Each callable should take 4 arguments: 

166 

167 1. a validator instance, 

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

169 instance 

170 3. the instance 

171 4. the schema 

172 

173 version: 

174 

175 an identifier for the version that this validator class will 

176 validate. If provided, the returned validator class will 

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

178 will have `jsonschema.validators.validates` automatically 

179 called for the given version. 

180 

181 type_checker: 

182 

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

184 

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

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

187 

188 format_checker: 

189 

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

191 

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

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

194 

195 id_of: 

196 

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

198 

199 applicable_validators: 

200 

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

202 applicable schema keywords and associated values 

203 which will be used to validate the instance. 

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

205 which specified behavior around ignoring keywords if they were 

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

207 implement similar behavior, you can typically ignore this argument 

208 and leave it at its default. 

209 

210 Returns: 

211 

212 a new `jsonschema.protocols.Validator` class 

213 

214 """ 

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

216 format_checker_arg = format_checker 

217 

218 specification = referencing.jsonschema.specification_with( 

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

220 default=referencing.Specification.OPAQUE, 

221 ) 

222 

223 @define 

224 class Validator: 

225 

226 VALIDATORS = dict(validators) # noqa: RUF012 

227 META_SCHEMA = dict(meta_schema) # noqa: RUF012 

228 TYPE_CHECKER = type_checker 

229 FORMAT_CHECKER = format_checker_arg 

230 ID_OF = staticmethod(id_of) 

231 

232 _APPLICABLE_VALIDATORS = applicable_validators 

233 _validators = field(init=False, repr=False, eq=False) 

234 

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

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

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

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

239 _registry: referencing.jsonschema.SchemaRegistry = field( 

240 default=_REMOTE_WARNING_REGISTRY, 

241 kw_only=True, 

242 repr=False, 

243 ) 

244 _resolver = field( 

245 alias="_resolver", 

246 default=None, 

247 kw_only=True, 

248 repr=False, 

249 ) 

250 

251 def __init_subclass__(cls): 

252 warnings.warn( 

253 ( 

254 "Subclassing validator classes is not intended to " 

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

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

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

258 "between releases of jsonschema. Instead, prefer " 

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

260 "owned entirely by the downstream library." 

261 ), 

262 DeprecationWarning, 

263 stacklevel=2, 

264 ) 

265 

266 def evolve(self, **changes): 

267 cls = self.__class__ 

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

269 NewValidator = validator_for(schema, default=cls) 

270 

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

272 if not field.init: 

273 continue 

274 attr_name = field.name 

275 init_name = field.alias 

276 if init_name not in changes: 

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

278 

279 return NewValidator(**changes) 

280 

281 cls.evolve = evolve 

282 

283 def __attrs_post_init__(self): 

284 if self._resolver is None: 

285 registry = self._registry 

286 if registry is not _REMOTE_WARNING_REGISTRY: 

287 registry = SPECIFICATIONS.combine(registry) 

288 resource = specification.create_resource(self.schema) 

289 self._resolver = registry.resolver_with_root(resource) 

290 

291 if self.schema is True or self.schema is False: 

292 self._validators = [] 

293 else: 

294 self._validators = [ 

295 (self.VALIDATORS[k], k, v) 

296 for k, v in applicable_validators(self.schema) 

297 if k in self.VALIDATORS 

298 ] 

299 

300 # REMOVEME: Legacy ref resolution state management. 

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

302 if push_scope is not None: 

303 id = id_of(self.schema) 

304 if id is not None: 

305 push_scope(id) 

306 

307 @classmethod 

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

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

310 if format_checker is _UNSET: 

311 format_checker = Validator.FORMAT_CHECKER 

312 validator = Validator( 

313 schema=cls.META_SCHEMA, 

314 format_checker=format_checker, 

315 ) 

316 for error in validator.iter_errors(schema): 

317 raise exceptions.SchemaError.create_from(error) 

318 

319 @property 

320 def resolver(self): 

321 warnings.warn( 

322 ( 

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

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

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

326 "library, which provides more compliant referencing " 

327 "behavior as well as more flexible APIs for " 

328 "customization." 

329 ), 

330 DeprecationWarning, 

331 stacklevel=2, 

332 ) 

333 if self._ref_resolver is None: 

334 self._ref_resolver = _RefResolver.from_schema( 

335 self.schema, 

336 id_of=id_of, 

337 ) 

338 return self._ref_resolver 

339 

340 def evolve(self, **changes): 

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

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

343 

344 for (attr_name, init_name) in evolve_fields: 

345 if init_name not in changes: 

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

347 

348 return NewValidator(**changes) 

349 

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

351 if _schema is not None: 

352 warnings.warn( 

353 ( 

354 "Passing a schema to Validator.iter_errors " 

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

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

357 "iter_errors(...) instead." 

358 ), 

359 DeprecationWarning, 

360 stacklevel=2, 

361 ) 

362 validators = [ 

363 (self.VALIDATORS[k], k, v) 

364 for k, v in applicable_validators(_schema) 

365 if k in self.VALIDATORS 

366 ] 

367 else: 

368 _schema, validators = self.schema, self._validators 

369 

370 if _schema is True: 

371 return 

372 elif _schema is False: 

373 yield exceptions.ValidationError( 

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

375 validator=None, 

376 validator_value=None, 

377 instance=instance, 

378 schema=_schema, 

379 ) 

380 return 

381 

382 for validator, k, v in validators: 

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

384 for error in errors: 

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

386 error._set( 

387 validator=k, 

388 validator_value=v, 

389 instance=instance, 

390 schema=_schema, 

391 type_checker=self.TYPE_CHECKER, 

392 ) 

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

394 error.schema_path.appendleft(k) 

395 yield error 

396 

397 def descend( 

398 self, 

399 instance, 

400 schema, 

401 path=None, 

402 schema_path=None, 

403 resolver=None, 

404 ): 

405 if schema is True: 

406 return 

407 elif schema is False: 

408 yield exceptions.ValidationError( 

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

410 validator=None, 

411 validator_value=None, 

412 instance=instance, 

413 schema=schema, 

414 ) 

415 return 

416 

417 if self._ref_resolver is not None: 

418 evolved = self.evolve(schema=schema) 

419 else: 

420 if resolver is None: 

421 resolver = self._resolver.in_subresource( 

422 specification.create_resource(schema), 

423 ) 

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

425 

426 for k, v in applicable_validators(schema): 

427 validator = evolved.VALIDATORS.get(k) 

428 if validator is None: 

429 continue 

430 

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

432 for error in errors: 

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

434 error._set( 

435 validator=k, 

436 validator_value=v, 

437 instance=instance, 

438 schema=schema, 

439 type_checker=evolved.TYPE_CHECKER, 

440 ) 

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

442 error.schema_path.appendleft(k) 

443 if path is not None: 

444 error.path.appendleft(path) 

445 if schema_path is not None: 

446 error.schema_path.appendleft(schema_path) 

447 yield error 

448 

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

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

451 raise error 

452 

453 def is_type(self, instance, type): 

454 try: 

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

456 except exceptions.UndefinedTypeCheck: 

457 exc = exceptions.UnknownType(type, instance, self.schema) 

458 raise exc from None 

459 

460 def _validate_reference(self, ref, instance): 

461 if self._ref_resolver is None: 

462 try: 

463 resolved = self._resolver.lookup(ref) 

464 except referencing.exceptions.Unresolvable as err: 

465 raise exceptions._WrappedReferencingError(err) from err 

466 

467 return self.descend( 

468 instance, 

469 resolved.contents, 

470 resolver=resolved.resolver, 

471 ) 

472 else: 

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

474 if resolve is None: 

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

476 return self.descend(instance, resolved) 

477 else: 

478 scope, resolved = resolve(ref) 

479 self._ref_resolver.push_scope(scope) 

480 

481 try: 

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

483 finally: 

484 self._ref_resolver.pop_scope() 

485 

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

487 if _schema is not None: 

488 warnings.warn( 

489 ( 

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

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

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

493 "instead." 

494 ), 

495 DeprecationWarning, 

496 stacklevel=2, 

497 ) 

498 self = self.evolve(schema=_schema) 

499 

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

501 return error is None 

502 

503 evolve_fields = [ 

504 (field.name, field.alias) 

505 for field in fields(Validator) 

506 if field.init 

507 ] 

508 

509 if version is not None: 

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

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

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

513 

514 return Validator 

515 

516 

517def extend( 

518 validator, 

519 validators=(), 

520 version=None, 

521 type_checker=None, 

522 format_checker=None, 

523): 

524 """ 

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

526 

527 Arguments: 

528 

529 validator (jsonschema.protocols.Validator): 

530 

531 an existing validator class 

532 

533 validators (collections.abc.Mapping): 

534 

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

536 structure is as in `create`. 

537 

538 .. note:: 

539 

540 Any validator callables with the same name as an 

541 existing one will (silently) replace the old validator 

542 callable entirely, effectively overriding any validation 

543 done in the "parent" validator class. 

544 

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

546 validator callable, delegate and call it directly in 

547 the new validator function by retrieving it using 

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

549 

550 version (str): 

551 

552 a version for the new validator class 

553 

554 type_checker (jsonschema.TypeChecker): 

555 

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

557 

558 If unprovided, the type checker of the extended 

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

560 

561 format_checker (jsonschema.FormatChecker): 

562 

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

564 

565 If unprovided, the format checker of the extended 

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

567 

568 Returns: 

569 

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

571 provided 

572 

573 .. note:: Meta Schemas 

574 

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

576 

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

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

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

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

581 old validator. 

582 

583 """ 

584 all_validators = dict(validator.VALIDATORS) 

585 all_validators.update(validators) 

586 

587 if type_checker is None: 

588 type_checker = validator.TYPE_CHECKER 

589 if format_checker is None: 

590 format_checker = validator.FORMAT_CHECKER 

591 return create( 

592 meta_schema=validator.META_SCHEMA, 

593 validators=all_validators, 

594 version=version, 

595 type_checker=type_checker, 

596 format_checker=format_checker, 

597 id_of=validator.ID_OF, 

598 applicable_validators=validator._APPLICABLE_VALIDATORS, 

599 ) 

600 

601 

602Draft3Validator = create( 

603 meta_schema=SPECIFICATIONS.contents( 

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

605 ), 

606 validators={ 

607 "$ref": _keywords.ref, 

608 "additionalItems": _legacy_keywords.additionalItems, 

609 "additionalProperties": _keywords.additionalProperties, 

610 "dependencies": _legacy_keywords.dependencies_draft3, 

611 "disallow": _legacy_keywords.disallow_draft3, 

612 "divisibleBy": _keywords.multipleOf, 

613 "enum": _keywords.enum, 

614 "extends": _legacy_keywords.extends_draft3, 

615 "format": _keywords.format, 

616 "items": _legacy_keywords.items_draft3_draft4, 

617 "maxItems": _keywords.maxItems, 

618 "maxLength": _keywords.maxLength, 

619 "maximum": _legacy_keywords.maximum_draft3_draft4, 

620 "minItems": _keywords.minItems, 

621 "minLength": _keywords.minLength, 

622 "minimum": _legacy_keywords.minimum_draft3_draft4, 

623 "pattern": _keywords.pattern, 

624 "patternProperties": _keywords.patternProperties, 

625 "properties": _legacy_keywords.properties_draft3, 

626 "type": _legacy_keywords.type_draft3, 

627 "uniqueItems": _keywords.uniqueItems, 

628 }, 

629 type_checker=_types.draft3_type_checker, 

630 format_checker=_format.draft3_format_checker, 

631 version="draft3", 

632 id_of=referencing.jsonschema.DRAFT3.id_of, 

633 applicable_validators=_legacy_keywords.ignore_ref_siblings, 

634) 

635 

636Draft4Validator = create( 

637 meta_schema=SPECIFICATIONS.contents( 

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

639 ), 

640 validators={ 

641 "$ref": _keywords.ref, 

642 "additionalItems": _legacy_keywords.additionalItems, 

643 "additionalProperties": _keywords.additionalProperties, 

644 "allOf": _keywords.allOf, 

645 "anyOf": _keywords.anyOf, 

646 "dependencies": _legacy_keywords.dependencies_draft4_draft6_draft7, 

647 "enum": _keywords.enum, 

648 "format": _keywords.format, 

649 "items": _legacy_keywords.items_draft3_draft4, 

650 "maxItems": _keywords.maxItems, 

651 "maxLength": _keywords.maxLength, 

652 "maxProperties": _keywords.maxProperties, 

653 "maximum": _legacy_keywords.maximum_draft3_draft4, 

654 "minItems": _keywords.minItems, 

655 "minLength": _keywords.minLength, 

656 "minProperties": _keywords.minProperties, 

657 "minimum": _legacy_keywords.minimum_draft3_draft4, 

658 "multipleOf": _keywords.multipleOf, 

659 "not": _keywords.not_, 

660 "oneOf": _keywords.oneOf, 

661 "pattern": _keywords.pattern, 

662 "patternProperties": _keywords.patternProperties, 

663 "properties": _keywords.properties, 

664 "required": _keywords.required, 

665 "type": _keywords.type, 

666 "uniqueItems": _keywords.uniqueItems, 

667 }, 

668 type_checker=_types.draft4_type_checker, 

669 format_checker=_format.draft4_format_checker, 

670 version="draft4", 

671 id_of=referencing.jsonschema.DRAFT4.id_of, 

672 applicable_validators=_legacy_keywords.ignore_ref_siblings, 

673) 

674 

675Draft6Validator = create( 

676 meta_schema=SPECIFICATIONS.contents( 

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

678 ), 

679 validators={ 

680 "$ref": _keywords.ref, 

681 "additionalItems": _legacy_keywords.additionalItems, 

682 "additionalProperties": _keywords.additionalProperties, 

683 "allOf": _keywords.allOf, 

684 "anyOf": _keywords.anyOf, 

685 "const": _keywords.const, 

686 "contains": _legacy_keywords.contains_draft6_draft7, 

687 "dependencies": _legacy_keywords.dependencies_draft4_draft6_draft7, 

688 "enum": _keywords.enum, 

689 "exclusiveMaximum": _keywords.exclusiveMaximum, 

690 "exclusiveMinimum": _keywords.exclusiveMinimum, 

691 "format": _keywords.format, 

692 "items": _legacy_keywords.items_draft6_draft7_draft201909, 

693 "maxItems": _keywords.maxItems, 

694 "maxLength": _keywords.maxLength, 

695 "maxProperties": _keywords.maxProperties, 

696 "maximum": _keywords.maximum, 

697 "minItems": _keywords.minItems, 

698 "minLength": _keywords.minLength, 

699 "minProperties": _keywords.minProperties, 

700 "minimum": _keywords.minimum, 

701 "multipleOf": _keywords.multipleOf, 

702 "not": _keywords.not_, 

703 "oneOf": _keywords.oneOf, 

704 "pattern": _keywords.pattern, 

705 "patternProperties": _keywords.patternProperties, 

706 "properties": _keywords.properties, 

707 "propertyNames": _keywords.propertyNames, 

708 "required": _keywords.required, 

709 "type": _keywords.type, 

710 "uniqueItems": _keywords.uniqueItems, 

711 }, 

712 type_checker=_types.draft6_type_checker, 

713 format_checker=_format.draft6_format_checker, 

714 version="draft6", 

715 id_of=referencing.jsonschema.DRAFT6.id_of, 

716 applicable_validators=_legacy_keywords.ignore_ref_siblings, 

717) 

718 

719Draft7Validator = create( 

720 meta_schema=SPECIFICATIONS.contents( 

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

722 ), 

723 validators={ 

724 "$ref": _keywords.ref, 

725 "additionalItems": _legacy_keywords.additionalItems, 

726 "additionalProperties": _keywords.additionalProperties, 

727 "allOf": _keywords.allOf, 

728 "anyOf": _keywords.anyOf, 

729 "const": _keywords.const, 

730 "contains": _legacy_keywords.contains_draft6_draft7, 

731 "dependencies": _legacy_keywords.dependencies_draft4_draft6_draft7, 

732 "enum": _keywords.enum, 

733 "exclusiveMaximum": _keywords.exclusiveMaximum, 

734 "exclusiveMinimum": _keywords.exclusiveMinimum, 

735 "format": _keywords.format, 

736 "if": _keywords.if_, 

737 "items": _legacy_keywords.items_draft6_draft7_draft201909, 

738 "maxItems": _keywords.maxItems, 

739 "maxLength": _keywords.maxLength, 

740 "maxProperties": _keywords.maxProperties, 

741 "maximum": _keywords.maximum, 

742 "minItems": _keywords.minItems, 

743 "minLength": _keywords.minLength, 

744 "minProperties": _keywords.minProperties, 

745 "minimum": _keywords.minimum, 

746 "multipleOf": _keywords.multipleOf, 

747 "not": _keywords.not_, 

748 "oneOf": _keywords.oneOf, 

749 "pattern": _keywords.pattern, 

750 "patternProperties": _keywords.patternProperties, 

751 "properties": _keywords.properties, 

752 "propertyNames": _keywords.propertyNames, 

753 "required": _keywords.required, 

754 "type": _keywords.type, 

755 "uniqueItems": _keywords.uniqueItems, 

756 }, 

757 type_checker=_types.draft7_type_checker, 

758 format_checker=_format.draft7_format_checker, 

759 version="draft7", 

760 id_of=referencing.jsonschema.DRAFT7.id_of, 

761 applicable_validators=_legacy_keywords.ignore_ref_siblings, 

762) 

763 

764Draft201909Validator = create( 

765 meta_schema=SPECIFICATIONS.contents( 

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

767 ), 

768 validators={ 

769 "$recursiveRef": _legacy_keywords.recursiveRef, 

770 "$ref": _keywords.ref, 

771 "additionalItems": _legacy_keywords.additionalItems, 

772 "additionalProperties": _keywords.additionalProperties, 

773 "allOf": _keywords.allOf, 

774 "anyOf": _keywords.anyOf, 

775 "const": _keywords.const, 

776 "contains": _keywords.contains, 

777 "dependentRequired": _keywords.dependentRequired, 

778 "dependentSchemas": _keywords.dependentSchemas, 

779 "enum": _keywords.enum, 

780 "exclusiveMaximum": _keywords.exclusiveMaximum, 

781 "exclusiveMinimum": _keywords.exclusiveMinimum, 

782 "format": _keywords.format, 

783 "if": _keywords.if_, 

784 "items": _legacy_keywords.items_draft6_draft7_draft201909, 

785 "maxItems": _keywords.maxItems, 

786 "maxLength": _keywords.maxLength, 

787 "maxProperties": _keywords.maxProperties, 

788 "maximum": _keywords.maximum, 

789 "minItems": _keywords.minItems, 

790 "minLength": _keywords.minLength, 

791 "minProperties": _keywords.minProperties, 

792 "minimum": _keywords.minimum, 

793 "multipleOf": _keywords.multipleOf, 

794 "not": _keywords.not_, 

795 "oneOf": _keywords.oneOf, 

796 "pattern": _keywords.pattern, 

797 "patternProperties": _keywords.patternProperties, 

798 "properties": _keywords.properties, 

799 "propertyNames": _keywords.propertyNames, 

800 "required": _keywords.required, 

801 "type": _keywords.type, 

802 "unevaluatedItems": _legacy_keywords.unevaluatedItems_draft2019, 

803 "unevaluatedProperties": ( 

804 _legacy_keywords.unevaluatedProperties_draft2019 

805 ), 

806 "uniqueItems": _keywords.uniqueItems, 

807 }, 

808 type_checker=_types.draft201909_type_checker, 

809 format_checker=_format.draft201909_format_checker, 

810 version="draft2019-09", 

811) 

812 

813Draft202012Validator = create( 

814 meta_schema=SPECIFICATIONS.contents( 

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

816 ), 

817 validators={ 

818 "$dynamicRef": _keywords.dynamicRef, 

819 "$ref": _keywords.ref, 

820 "additionalProperties": _keywords.additionalProperties, 

821 "allOf": _keywords.allOf, 

822 "anyOf": _keywords.anyOf, 

823 "const": _keywords.const, 

824 "contains": _keywords.contains, 

825 "dependentRequired": _keywords.dependentRequired, 

826 "dependentSchemas": _keywords.dependentSchemas, 

827 "enum": _keywords.enum, 

828 "exclusiveMaximum": _keywords.exclusiveMaximum, 

829 "exclusiveMinimum": _keywords.exclusiveMinimum, 

830 "format": _keywords.format, 

831 "if": _keywords.if_, 

832 "items": _keywords.items, 

833 "maxItems": _keywords.maxItems, 

834 "maxLength": _keywords.maxLength, 

835 "maxProperties": _keywords.maxProperties, 

836 "maximum": _keywords.maximum, 

837 "minItems": _keywords.minItems, 

838 "minLength": _keywords.minLength, 

839 "minProperties": _keywords.minProperties, 

840 "minimum": _keywords.minimum, 

841 "multipleOf": _keywords.multipleOf, 

842 "not": _keywords.not_, 

843 "oneOf": _keywords.oneOf, 

844 "pattern": _keywords.pattern, 

845 "patternProperties": _keywords.patternProperties, 

846 "prefixItems": _keywords.prefixItems, 

847 "properties": _keywords.properties, 

848 "propertyNames": _keywords.propertyNames, 

849 "required": _keywords.required, 

850 "type": _keywords.type, 

851 "unevaluatedItems": _keywords.unevaluatedItems, 

852 "unevaluatedProperties": _keywords.unevaluatedProperties, 

853 "uniqueItems": _keywords.uniqueItems, 

854 }, 

855 type_checker=_types.draft202012_type_checker, 

856 format_checker=_format.draft202012_format_checker, 

857 version="draft2020-12", 

858) 

859 

860_LATEST_VERSION = Draft202012Validator 

861 

862 

863class _RefResolver: 

864 """ 

865 Resolve JSON References. 

866 

867 Arguments: 

868 

869 base_uri (str): 

870 

871 The URI of the referring document 

872 

873 referrer: 

874 

875 The actual referring document 

876 

877 store (dict): 

878 

879 A mapping from URIs to documents to cache 

880 

881 cache_remote (bool): 

882 

883 Whether remote refs should be cached after first resolution 

884 

885 handlers (dict): 

886 

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

888 to retrieve them 

889 

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

891 

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

893 the resolution scope to subscopes. 

894 

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

896 

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

898 resolved remote URLs. 

899 

900 Attributes: 

901 

902 cache_remote (bool): 

903 

904 Whether remote refs should be cached after first resolution 

905 

906 .. deprecated:: v4.18.0 

907 

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

909 

910 """ 

911 

912 _DEPRECATION_MESSAGE = ( 

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

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

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

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

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

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

919 ) 

920 

921 def __init__( 

922 self, 

923 base_uri, 

924 referrer, 

925 store=HashTrieMap(), 

926 cache_remote=True, 

927 handlers=(), 

928 urljoin_cache=None, 

929 remote_cache=None, 

930 ): 

931 if urljoin_cache is None: 

932 urljoin_cache = lru_cache(1024)(urljoin) 

933 if remote_cache is None: 

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

935 

936 self.referrer = referrer 

937 self.cache_remote = cache_remote 

938 self.handlers = dict(handlers) 

939 

940 self._scopes_stack = [base_uri] 

941 

942 self.store = _utils.URIDict( 

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

944 ) 

945 self.store.update( 

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

947 ) 

948 self.store.update(store) 

949 self.store.update( 

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

951 for schema in store.values() 

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

953 ) 

954 self.store[base_uri] = referrer 

955 

956 self._urljoin_cache = urljoin_cache 

957 self._remote_cache = remote_cache 

958 

959 @classmethod 

960 def from_schema( # noqa: D417 

961 cls, 

962 schema, 

963 id_of=referencing.jsonschema.DRAFT202012.id_of, 

964 *args, 

965 **kwargs, 

966 ): 

967 """ 

968 Construct a resolver from a JSON schema object. 

969 

970 Arguments: 

971 

972 schema: 

973 

974 the referring schema 

975 

976 Returns: 

977 

978 `_RefResolver` 

979 

980 """ 

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

982 

983 def push_scope(self, scope): 

984 """ 

985 Enter a given sub-scope. 

986 

987 Treats further dereferences as being performed underneath the 

988 given scope. 

989 """ 

990 self._scopes_stack.append( 

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

992 ) 

993 

994 def pop_scope(self): 

995 """ 

996 Exit the most recent entered scope. 

997 

998 Treats further dereferences as being performed underneath the 

999 original scope. 

1000 

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

1002 called. 

1003 """ 

1004 try: 

1005 self._scopes_stack.pop() 

1006 except IndexError: 

1007 raise exceptions._RefResolutionError( 

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

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

1010 "`push_scope()`", 

1011 ) from None 

1012 

1013 @property 

1014 def resolution_scope(self): 

1015 """ 

1016 Retrieve the current resolution scope. 

1017 """ 

1018 return self._scopes_stack[-1] 

1019 

1020 @property 

1021 def base_uri(self): 

1022 """ 

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

1024 """ 

1025 uri, _ = urldefrag(self.resolution_scope) 

1026 return uri 

1027 

1028 @contextlib.contextmanager 

1029 def in_scope(self, scope): 

1030 """ 

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

1032 

1033 .. deprecated:: v4.0.0 

1034 """ 

1035 warnings.warn( 

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

1037 "removed in a future release.", 

1038 DeprecationWarning, 

1039 stacklevel=3, 

1040 ) 

1041 self.push_scope(scope) 

1042 try: 

1043 yield 

1044 finally: 

1045 self.pop_scope() 

1046 

1047 @contextlib.contextmanager 

1048 def resolving(self, ref): 

1049 """ 

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

1051 

1052 Exits the scope on exit of this context manager. 

1053 

1054 Arguments: 

1055 

1056 ref (str): 

1057 

1058 The reference to resolve 

1059 

1060 """ 

1061 url, resolved = self.resolve(ref) 

1062 self.push_scope(url) 

1063 try: 

1064 yield resolved 

1065 finally: 

1066 self.pop_scope() 

1067 

1068 def _find_in_referrer(self, key): 

1069 return self._get_subschemas_cache()[key] 

1070 

1071 @lru_cache # noqa: B019 

1072 def _get_subschemas_cache(self): 

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

1074 for keyword, subschema in _search_schema( 

1075 self.referrer, _match_subschema_keywords, 

1076 ): 

1077 cache[keyword].append(subschema) 

1078 return cache 

1079 

1080 @lru_cache # noqa: B019 

1081 def _find_in_subschemas(self, url): 

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

1083 if not subschemas: 

1084 return None 

1085 uri, fragment = urldefrag(url) 

1086 for subschema in subschemas: 

1087 id = subschema["$id"] 

1088 if not isinstance(id, str): 

1089 continue 

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

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

1092 if fragment: 

1093 subschema = self.resolve_fragment(subschema, fragment) 

1094 self.store[url] = subschema 

1095 return url, subschema 

1096 return None 

1097 

1098 def resolve(self, ref): 

1099 """ 

1100 Resolve the given reference. 

1101 """ 

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

1103 

1104 match = self._find_in_subschemas(url) 

1105 if match is not None: 

1106 return match 

1107 

1108 return url, self._remote_cache(url) 

1109 

1110 def resolve_from_url(self, url): 

1111 """ 

1112 Resolve the given URL. 

1113 """ 

1114 url, fragment = urldefrag(url) 

1115 if not url: 

1116 url = self.base_uri 

1117 

1118 try: 

1119 document = self.store[url] 

1120 except KeyError: 

1121 try: 

1122 document = self.resolve_remote(url) 

1123 except Exception as exc: 

1124 raise exceptions._RefResolutionError(exc) from exc 

1125 

1126 return self.resolve_fragment(document, fragment) 

1127 

1128 def resolve_fragment(self, document, fragment): 

1129 """ 

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

1131 

1132 Arguments: 

1133 

1134 document: 

1135 

1136 The referent document 

1137 

1138 fragment (str): 

1139 

1140 a URI fragment to resolve within it 

1141 

1142 """ 

1143 fragment = fragment.lstrip("/") 

1144 

1145 if not fragment: 

1146 return document 

1147 

1148 if document is self.referrer: 

1149 find = self._find_in_referrer 

1150 else: 

1151 

1152 def find(key): 

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

1154 

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

1156 for subschema in find(keyword): 

1157 if fragment == subschema[keyword]: 

1158 return subschema 

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

1160 for subschema in find(keyword): 

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

1162 return subschema 

1163 

1164 # Resolve via path 

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

1166 for part in parts: 

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

1168 

1169 if isinstance(document, Sequence): 

1170 try: # noqa: SIM105 

1171 part = int(part) 

1172 except ValueError: 

1173 pass 

1174 try: 

1175 document = document[part] 

1176 except (TypeError, LookupError) as err: 

1177 raise exceptions._RefResolutionError( 

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

1179 ) from err 

1180 

1181 return document 

1182 

1183 def resolve_remote(self, uri): 

1184 """ 

1185 Resolve a remote ``uri``. 

1186 

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

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

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

1190 

1191 .. note:: 

1192 

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

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

1195 detected and used. 

1196 

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

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

1199 

1200 Arguments: 

1201 

1202 uri (str): 

1203 

1204 The URI to resolve 

1205 

1206 Returns: 

1207 

1208 The retrieved document 

1209 

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

1211 

1212 """ 

1213 try: 

1214 import requests 

1215 except ImportError: 

1216 requests = None 

1217 

1218 scheme = urlsplit(uri).scheme 

1219 

1220 if scheme in self.handlers: 

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

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

1223 # Requests has support for detecting the correct encoding of 

1224 # json over http 

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

1226 else: 

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

1228 with urlopen(uri) as url: # noqa: S310 

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

1230 

1231 if self.cache_remote: 

1232 self.store[uri] = result 

1233 return result 

1234 

1235 

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

1237 

1238 

1239def _match_keyword(keyword): 

1240 

1241 def matcher(value): 

1242 if keyword in value: 

1243 yield value 

1244 

1245 return matcher 

1246 

1247 

1248def _match_subschema_keywords(value): 

1249 for keyword in _SUBSCHEMAS_KEYWORDS: 

1250 if keyword in value: 

1251 yield keyword, value 

1252 

1253 

1254def _search_schema(schema, matcher): 

1255 """Breadth-first search routine.""" 

1256 values = deque([schema]) 

1257 while values: 

1258 value = values.pop() 

1259 if not isinstance(value, dict): 

1260 continue 

1261 yield from matcher(value) 

1262 values.extendleft(value.values()) 

1263 

1264 

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

1266 """ 

1267 Validate an instance under the given schema. 

1268 

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

1270 Traceback (most recent call last): 

1271 ... 

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

1273 

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

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

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

1277 

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

1279 if you intend to validate multiple instances with 

1280 the same schema, you likely would prefer using the 

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

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

1283 

1284 

1285 Arguments: 

1286 

1287 instance: 

1288 

1289 The instance to validate 

1290 

1291 schema: 

1292 

1293 The schema to validate with 

1294 

1295 cls (jsonschema.protocols.Validator): 

1296 

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

1298 

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

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

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

1302 proper validator will be used. The specification recommends that 

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

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

1305 latest released draft. 

1306 

1307 Any other provided positional and keyword arguments will be passed 

1308 on when instantiating the ``cls``. 

1309 

1310 Raises: 

1311 

1312 `jsonschema.exceptions.ValidationError`: 

1313 

1314 if the instance is invalid 

1315 

1316 `jsonschema.exceptions.SchemaError`: 

1317 

1318 if the schema itself is invalid 

1319 

1320 .. rubric:: Footnotes 

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

1322 `jsonschema.validators.validates` 

1323 

1324 """ 

1325 if cls is None: 

1326 cls = validator_for(schema) 

1327 

1328 cls.check_schema(schema) 

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

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

1331 if error is not None: 

1332 raise error 

1333 

1334 

1335def validator_for( 

1336 schema, 

1337 default: Validator | _utils.Unset = _UNSET, 

1338) -> type[Validator]: 

1339 """ 

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

1341 

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

1343 schema to look up the appropriate validator class. 

1344 

1345 Arguments: 

1346 

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

1348 

1349 the schema to look at 

1350 

1351 default: 

1352 

1353 the default to return if the appropriate validator class 

1354 cannot be determined. 

1355 

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

1357 draft. 

1358 

1359 Examples: 

1360 

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

1362 class is returned: 

1363 

1364 >>> schema = { 

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

1366 ... "type": "integer", 

1367 ... } 

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

1369 <class 'jsonschema.validators.Draft202012Validator'> 

1370 

1371 

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

1373 

1374 >>> schema = { 

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

1376 ... "type": "integer", 

1377 ... } 

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

1379 <class 'jsonschema.validators.Draft7Validator'> 

1380 

1381 

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

1383 argument: 

1384 

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

1386 >>> jsonschema.validators.validator_for( 

1387 ... schema, default=Draft7Validator, 

1388 ... ) 

1389 <class 'jsonschema.validators.Draft7Validator'> 

1390 

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

1392 Always including the keyword when authoring schemas is highly 

1393 recommended. 

1394 

1395 """ 

1396 DefaultValidator = _LATEST_VERSION if default is _UNSET else default 

1397 

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

1399 return DefaultValidator 

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

1401 warn( 

1402 ( 

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

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

1405 "an error in the future." 

1406 ), 

1407 DeprecationWarning, 

1408 stacklevel=2, 

1409 ) 

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