Coverage for /pythoncovmergedfiles/medio/medio/src/jsonschema/jsonschema/validators.py: 45%

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

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

33 

34if TYPE_CHECKING: 

35 from jsonschema.protocols import Validator 

36 

37_UNSET = _utils.Unset() 

38 

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

40_META_SCHEMAS = _utils.URIDict() 

41 

42 

43def __getattr__(name): 

44 if name == "ErrorTree": 

45 warnings.warn( 

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

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

48 DeprecationWarning, 

49 stacklevel=2, 

50 ) 

51 from jsonschema.exceptions import ErrorTree 

52 return ErrorTree 

53 elif name == "validators": 

54 warnings.warn( 

55 "Accessing jsonschema.validators.validators is deprecated. " 

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

57 DeprecationWarning, 

58 stacklevel=2, 

59 ) 

60 return _VALIDATORS 

61 elif name == "meta_schemas": 

62 warnings.warn( 

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

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

65 DeprecationWarning, 

66 stacklevel=2, 

67 ) 

68 return _META_SCHEMAS 

69 elif name == "RefResolver": 

70 warnings.warn( 

71 _RefResolver._DEPRECATION_MESSAGE, 

72 DeprecationWarning, 

73 stacklevel=2, 

74 ) 

75 return _RefResolver 

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

77 

78 

79def validates(version): 

80 """ 

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

82 

83 Registered validators and their meta schemas will be considered when 

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

85 

86 Arguments: 

87 

88 version (str): 

89 

90 An identifier to use as the version's name 

91 

92 Returns: 

93 

94 collections.abc.Callable: 

95 

96 a class decorator to decorate the validator with the version 

97 

98 """ 

99 

100 def _validates(cls): 

101 _VALIDATORS[version] = cls 

102 meta_schema_id = cls.ID_OF(cls.META_SCHEMA) 

103 _META_SCHEMAS[meta_schema_id] = cls 

104 return cls 

105 return _validates 

106 

107 

108def _warn_for_remote_retrieve(uri: str): 

109 from urllib.request import Request, urlopen 

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

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

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

113 warnings.warn( 

114 "Automatically retrieving remote references can be a security " 

115 "vulnerability and is discouraged by the JSON Schema " 

116 "specifications. Relying on this behavior is deprecated " 

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

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

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

120 "in the referencing documentation " 

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

122 DeprecationWarning, 

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

124 ) 

125 return referencing.Resource.from_contents( 

126 json.load(response), 

127 default_specification=referencing.jsonschema.DRAFT202012, 

128 ) 

129 

130 

131_REMOTE_WARNING_REGISTRY = SPECIFICATIONS.combine( 

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

133) 

134 

135 

136def create( 

137 meta_schema: referencing.jsonschema.ObjectSchema, 

138 validators: ( 

139 Mapping[str, _typing.SchemaKeywordValidator] 

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

141 ) = (), 

142 version: str | None = None, 

143 type_checker: _types.TypeChecker = _types.draft202012_type_checker, 

144 format_checker: _format.FormatChecker = _format.draft202012_format_checker, 

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

146 applicable_validators: _typing.ApplicableValidators = methodcaller( 

147 "items", 

148 ), 

149) -> type[Validator]: 

150 """ 

151 Create a new validator class. 

152 

153 Arguments: 

154 

155 meta_schema: 

156 

157 the meta schema for the new validator class 

158 

159 validators: 

160 

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

162 validate the schema property with the given name. 

163 

164 Each callable should take 4 arguments: 

165 

166 1. a validator instance, 

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

168 instance 

169 3. the instance 

170 4. the schema 

171 

172 version: 

173 

174 an identifier for the version that this validator class will 

175 validate. If provided, the returned validator class will 

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

177 will have `jsonschema.validators.validates` automatically 

178 called for the given version. 

179 

180 type_checker: 

181 

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

183 

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

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

186 

187 format_checker: 

188 

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

190 

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

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

193 

194 id_of: 

195 

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

197 

198 applicable_validators: 

199 

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

201 applicable schema keywords and associated values 

202 which will be used to validate the instance. 

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

204 which specified behavior around ignoring keywords if they were 

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

206 implement similar behavior, you can typically ignore this argument 

207 and leave it at its default. 

208 

209 Returns: 

210 

211 a new `jsonschema.protocols.Validator` class 

212 

213 """ 

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

215 format_checker_arg = format_checker 

216 

217 specification = referencing.jsonschema.specification_with( 

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

219 default=referencing.Specification.OPAQUE, 

220 ) 

221 

222 @define 

223 class Validator: 

224 

225 VALIDATORS = dict(validators) # noqa: RUF012 

226 META_SCHEMA = dict(meta_schema) # noqa: RUF012 

227 TYPE_CHECKER = type_checker 

228 FORMAT_CHECKER = format_checker_arg 

229 ID_OF = staticmethod(id_of) 

230 

231 _APPLICABLE_VALIDATORS = applicable_validators 

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

233 

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

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

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

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

238 _registry: referencing.jsonschema.SchemaRegistry = field( 

239 default=_REMOTE_WARNING_REGISTRY, 

240 kw_only=True, 

241 repr=False, 

242 ) 

243 _resolver = field( 

244 alias="_resolver", 

245 default=None, 

246 kw_only=True, 

247 repr=False, 

248 ) 

249 

250 def __init_subclass__(cls): 

251 warnings.warn( 

252 ( 

253 "Subclassing validator classes is not intended to " 

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

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

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

257 "between releases of jsonschema. Instead, prefer " 

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

259 "owned entirely by the downstream library." 

260 ), 

261 DeprecationWarning, 

262 stacklevel=2, 

263 ) 

264 

265 def evolve(self, **changes): 

266 cls = self.__class__ 

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

268 NewValidator = validator_for(schema, default=cls) 

269 

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

271 if not field.init: 

272 continue 

273 attr_name = field.name 

274 init_name = field.alias 

275 if init_name not in changes: 

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

277 

278 return NewValidator(**changes) 

279 

280 cls.evolve = evolve 

281 

282 def __attrs_post_init__(self): 

283 if self._resolver is None: 

284 registry = self._registry 

285 if registry is not _REMOTE_WARNING_REGISTRY: 

286 registry = SPECIFICATIONS.combine(registry) 

287 resource = specification.create_resource(self.schema) 

288 self._resolver = registry.resolver_with_root(resource) 

289 

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

291 self._validators = [] 

292 else: 

293 self._validators = [ 

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

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

296 if k in self.VALIDATORS 

297 ] 

298 

299 # REMOVEME: Legacy ref resolution state management. 

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

301 if push_scope is not None: 

302 id = id_of(self.schema) 

303 if id is not None: 

304 push_scope(id) 

305 

306 @classmethod 

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

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

309 if format_checker is _UNSET: 

310 format_checker = Validator.FORMAT_CHECKER 

311 validator = Validator( 

312 schema=cls.META_SCHEMA, 

313 format_checker=format_checker, 

314 ) 

315 for error in validator.iter_errors(schema): 

316 raise exceptions.SchemaError.create_from(error) 

317 

318 @property 

319 def resolver(self): 

320 warnings.warn( 

321 ( 

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

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

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

325 "library, which provides more compliant referencing " 

326 "behavior as well as more flexible APIs for " 

327 "customization." 

328 ), 

329 DeprecationWarning, 

330 stacklevel=2, 

331 ) 

332 if self._ref_resolver is None: 

333 self._ref_resolver = _RefResolver.from_schema( 

334 self.schema, 

335 id_of=id_of, 

336 ) 

337 return self._ref_resolver 

338 

339 def evolve(self, **changes): 

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

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

342 

343 for (attr_name, init_name) in evolve_fields: 

344 if init_name not in changes: 

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

346 

347 return NewValidator(**changes) 

348 

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

350 if _schema is not None: 

351 warnings.warn( 

352 ( 

353 "Passing a schema to Validator.iter_errors " 

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

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

356 "iter_errors(...) instead." 

357 ), 

358 DeprecationWarning, 

359 stacklevel=2, 

360 ) 

361 validators = [ 

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

363 for k, v in applicable_validators(_schema) 

364 if k in self.VALIDATORS 

365 ] 

366 else: 

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

368 

369 if _schema is True: 

370 return 

371 elif _schema is False: 

372 yield exceptions.ValidationError( 

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

374 validator=None, 

375 validator_value=None, 

376 instance=instance, 

377 schema=_schema, 

378 ) 

379 return 

380 

381 for validator, k, v in validators: 

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

383 for error in errors: 

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

385 error._set( 

386 validator=k, 

387 validator_value=v, 

388 instance=instance, 

389 schema=_schema, 

390 type_checker=self.TYPE_CHECKER, 

391 ) 

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

393 error.schema_path.appendleft(k) 

394 yield error 

395 

396 def descend( 

397 self, 

398 instance, 

399 schema, 

400 path=None, 

401 schema_path=None, 

402 resolver=None, 

403 ): 

404 if schema is True: 

405 return 

406 elif schema is False: 

407 yield exceptions.ValidationError( 

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

409 validator=None, 

410 validator_value=None, 

411 instance=instance, 

412 schema=schema, 

413 path=() if path is None else (path,), 

414 schema_path=() if schema_path is None else (schema_path,), 

415 ) 

416 return 

417 

418 if self._ref_resolver is not None: 

419 evolved = self.evolve(schema=schema) 

420 else: 

421 if resolver is None: 

422 resolver = self._resolver.in_subresource( 

423 specification.create_resource(schema), 

424 ) 

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

426 

427 for k, v in applicable_validators(schema): 

428 validator = evolved.VALIDATORS.get(k) 

429 if validator is None: 

430 continue 

431 

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

433 for error in errors: 

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

435 error._set( 

436 validator=k, 

437 validator_value=v, 

438 instance=instance, 

439 schema=schema, 

440 type_checker=evolved.TYPE_CHECKER, 

441 ) 

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

443 error.schema_path.appendleft(k) 

444 if path is not None: 

445 error.path.appendleft(path) 

446 if schema_path is not None: 

447 error.schema_path.appendleft(schema_path) 

448 yield error 

449 

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

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

452 raise error 

453 

454 def is_type(self, instance, type): 

455 try: 

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

457 except exceptions.UndefinedTypeCheck: 

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

459 raise exc from None 

460 

461 def _validate_reference(self, ref, instance): 

462 if self._ref_resolver is None: 

463 try: 

464 resolved = self._resolver.lookup(ref) 

465 except referencing.exceptions.Unresolvable as err: 

466 raise exceptions._WrappedReferencingError(err) from err 

467 

468 return self.descend( 

469 instance, 

470 resolved.contents, 

471 resolver=resolved.resolver, 

472 ) 

473 else: 

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

475 if resolve is None: 

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

477 return self.descend(instance, resolved) 

478 else: 

479 scope, resolved = resolve(ref) 

480 self._ref_resolver.push_scope(scope) 

481 

482 try: 

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

484 finally: 

485 self._ref_resolver.pop_scope() 

486 

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

488 if _schema is not None: 

489 warnings.warn( 

490 ( 

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

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

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

494 "instead." 

495 ), 

496 DeprecationWarning, 

497 stacklevel=2, 

498 ) 

499 self = self.evolve(schema=_schema) 

500 

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

502 return error is None 

503 

504 evolve_fields = [ 

505 (field.name, field.alias) 

506 for field in fields(Validator) 

507 if field.init 

508 ] 

509 

510 if version is not None: 

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

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

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

514 

515 return Validator # type: ignore[return-value] 

516 

517 

518def extend( 

519 validator, 

520 validators=(), 

521 version=None, 

522 type_checker=None, 

523 format_checker=None, 

524): 

525 """ 

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

527 

528 Arguments: 

529 

530 validator (jsonschema.protocols.Validator): 

531 

532 an existing validator class 

533 

534 validators (collections.abc.Mapping): 

535 

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

537 structure is as in `create`. 

538 

539 .. note:: 

540 

541 Any validator callables with the same name as an 

542 existing one will (silently) replace the old validator 

543 callable entirely, effectively overriding any validation 

544 done in the "parent" validator class. 

545 

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

547 validator callable, delegate and call it directly in 

548 the new validator function by retrieving it using 

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

550 

551 version (str): 

552 

553 a version for the new validator class 

554 

555 type_checker (jsonschema.TypeChecker): 

556 

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

558 

559 If unprovided, the type checker of the extended 

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

561 

562 format_checker (jsonschema.FormatChecker): 

563 

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

565 

566 If unprovided, the format checker of the extended 

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

568 

569 Returns: 

570 

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

572 provided 

573 

574 .. note:: Meta Schemas 

575 

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

577 

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

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

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

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

582 old validator. 

583 

584 """ 

585 all_validators = dict(validator.VALIDATORS) 

586 all_validators.update(validators) 

587 

588 if type_checker is None: 

589 type_checker = validator.TYPE_CHECKER 

590 if format_checker is None: 

591 format_checker = validator.FORMAT_CHECKER 

592 return create( 

593 meta_schema=validator.META_SCHEMA, 

594 validators=all_validators, 

595 version=version, 

596 type_checker=type_checker, 

597 format_checker=format_checker, 

598 id_of=validator.ID_OF, 

599 applicable_validators=validator._APPLICABLE_VALIDATORS, 

600 ) 

601 

602 

603Draft3Validator = create( 

604 meta_schema=SPECIFICATIONS.contents( 

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

606 ), 

607 validators={ 

608 "$ref": _keywords.ref, 

609 "additionalItems": _legacy_keywords.additionalItems, 

610 "additionalProperties": _keywords.additionalProperties, 

611 "dependencies": _legacy_keywords.dependencies_draft3, 

612 "disallow": _legacy_keywords.disallow_draft3, 

613 "divisibleBy": _keywords.multipleOf, 

614 "enum": _keywords.enum, 

615 "extends": _legacy_keywords.extends_draft3, 

616 "format": _keywords.format, 

617 "items": _legacy_keywords.items_draft3_draft4, 

618 "maxItems": _keywords.maxItems, 

619 "maxLength": _keywords.maxLength, 

620 "maximum": _legacy_keywords.maximum_draft3_draft4, 

621 "minItems": _keywords.minItems, 

622 "minLength": _keywords.minLength, 

623 "minimum": _legacy_keywords.minimum_draft3_draft4, 

624 "pattern": _keywords.pattern, 

625 "patternProperties": _keywords.patternProperties, 

626 "properties": _legacy_keywords.properties_draft3, 

627 "type": _legacy_keywords.type_draft3, 

628 "uniqueItems": _keywords.uniqueItems, 

629 }, 

630 type_checker=_types.draft3_type_checker, 

631 format_checker=_format.draft3_format_checker, 

632 version="draft3", 

633 id_of=referencing.jsonschema.DRAFT3.id_of, 

634 applicable_validators=_legacy_keywords.ignore_ref_siblings, 

635) 

636 

637Draft4Validator = create( 

638 meta_schema=SPECIFICATIONS.contents( 

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

640 ), 

641 validators={ 

642 "$ref": _keywords.ref, 

643 "additionalItems": _legacy_keywords.additionalItems, 

644 "additionalProperties": _keywords.additionalProperties, 

645 "allOf": _keywords.allOf, 

646 "anyOf": _keywords.anyOf, 

647 "dependencies": _legacy_keywords.dependencies_draft4_draft6_draft7, 

648 "enum": _keywords.enum, 

649 "format": _keywords.format, 

650 "items": _legacy_keywords.items_draft3_draft4, 

651 "maxItems": _keywords.maxItems, 

652 "maxLength": _keywords.maxLength, 

653 "maxProperties": _keywords.maxProperties, 

654 "maximum": _legacy_keywords.maximum_draft3_draft4, 

655 "minItems": _keywords.minItems, 

656 "minLength": _keywords.minLength, 

657 "minProperties": _keywords.minProperties, 

658 "minimum": _legacy_keywords.minimum_draft3_draft4, 

659 "multipleOf": _keywords.multipleOf, 

660 "not": _keywords.not_, 

661 "oneOf": _keywords.oneOf, 

662 "pattern": _keywords.pattern, 

663 "patternProperties": _keywords.patternProperties, 

664 "properties": _keywords.properties, 

665 "required": _keywords.required, 

666 "type": _keywords.type, 

667 "uniqueItems": _keywords.uniqueItems, 

668 }, 

669 type_checker=_types.draft4_type_checker, 

670 format_checker=_format.draft4_format_checker, 

671 version="draft4", 

672 id_of=referencing.jsonschema.DRAFT4.id_of, 

673 applicable_validators=_legacy_keywords.ignore_ref_siblings, 

674) 

675 

676Draft6Validator = create( 

677 meta_schema=SPECIFICATIONS.contents( 

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

679 ), 

680 validators={ 

681 "$ref": _keywords.ref, 

682 "additionalItems": _legacy_keywords.additionalItems, 

683 "additionalProperties": _keywords.additionalProperties, 

684 "allOf": _keywords.allOf, 

685 "anyOf": _keywords.anyOf, 

686 "const": _keywords.const, 

687 "contains": _legacy_keywords.contains_draft6_draft7, 

688 "dependencies": _legacy_keywords.dependencies_draft4_draft6_draft7, 

689 "enum": _keywords.enum, 

690 "exclusiveMaximum": _keywords.exclusiveMaximum, 

691 "exclusiveMinimum": _keywords.exclusiveMinimum, 

692 "format": _keywords.format, 

693 "items": _legacy_keywords.items_draft6_draft7_draft201909, 

694 "maxItems": _keywords.maxItems, 

695 "maxLength": _keywords.maxLength, 

696 "maxProperties": _keywords.maxProperties, 

697 "maximum": _keywords.maximum, 

698 "minItems": _keywords.minItems, 

699 "minLength": _keywords.minLength, 

700 "minProperties": _keywords.minProperties, 

701 "minimum": _keywords.minimum, 

702 "multipleOf": _keywords.multipleOf, 

703 "not": _keywords.not_, 

704 "oneOf": _keywords.oneOf, 

705 "pattern": _keywords.pattern, 

706 "patternProperties": _keywords.patternProperties, 

707 "properties": _keywords.properties, 

708 "propertyNames": _keywords.propertyNames, 

709 "required": _keywords.required, 

710 "type": _keywords.type, 

711 "uniqueItems": _keywords.uniqueItems, 

712 }, 

713 type_checker=_types.draft6_type_checker, 

714 format_checker=_format.draft6_format_checker, 

715 version="draft6", 

716 id_of=referencing.jsonschema.DRAFT6.id_of, 

717 applicable_validators=_legacy_keywords.ignore_ref_siblings, 

718) 

719 

720Draft7Validator = create( 

721 meta_schema=SPECIFICATIONS.contents( 

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

723 ), 

724 validators={ 

725 "$ref": _keywords.ref, 

726 "additionalItems": _legacy_keywords.additionalItems, 

727 "additionalProperties": _keywords.additionalProperties, 

728 "allOf": _keywords.allOf, 

729 "anyOf": _keywords.anyOf, 

730 "const": _keywords.const, 

731 "contains": _legacy_keywords.contains_draft6_draft7, 

732 "dependencies": _legacy_keywords.dependencies_draft4_draft6_draft7, 

733 "enum": _keywords.enum, 

734 "exclusiveMaximum": _keywords.exclusiveMaximum, 

735 "exclusiveMinimum": _keywords.exclusiveMinimum, 

736 "format": _keywords.format, 

737 "if": _keywords.if_, 

738 "items": _legacy_keywords.items_draft6_draft7_draft201909, 

739 "maxItems": _keywords.maxItems, 

740 "maxLength": _keywords.maxLength, 

741 "maxProperties": _keywords.maxProperties, 

742 "maximum": _keywords.maximum, 

743 "minItems": _keywords.minItems, 

744 "minLength": _keywords.minLength, 

745 "minProperties": _keywords.minProperties, 

746 "minimum": _keywords.minimum, 

747 "multipleOf": _keywords.multipleOf, 

748 "not": _keywords.not_, 

749 "oneOf": _keywords.oneOf, 

750 "pattern": _keywords.pattern, 

751 "patternProperties": _keywords.patternProperties, 

752 "properties": _keywords.properties, 

753 "propertyNames": _keywords.propertyNames, 

754 "required": _keywords.required, 

755 "type": _keywords.type, 

756 "uniqueItems": _keywords.uniqueItems, 

757 }, 

758 type_checker=_types.draft7_type_checker, 

759 format_checker=_format.draft7_format_checker, 

760 version="draft7", 

761 id_of=referencing.jsonschema.DRAFT7.id_of, 

762 applicable_validators=_legacy_keywords.ignore_ref_siblings, 

763) 

764 

765Draft201909Validator = create( 

766 meta_schema=SPECIFICATIONS.contents( 

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

768 ), 

769 validators={ 

770 "$recursiveRef": _legacy_keywords.recursiveRef, 

771 "$ref": _keywords.ref, 

772 "additionalItems": _legacy_keywords.additionalItems, 

773 "additionalProperties": _keywords.additionalProperties, 

774 "allOf": _keywords.allOf, 

775 "anyOf": _keywords.anyOf, 

776 "const": _keywords.const, 

777 "contains": _keywords.contains, 

778 "dependentRequired": _keywords.dependentRequired, 

779 "dependentSchemas": _keywords.dependentSchemas, 

780 "enum": _keywords.enum, 

781 "exclusiveMaximum": _keywords.exclusiveMaximum, 

782 "exclusiveMinimum": _keywords.exclusiveMinimum, 

783 "format": _keywords.format, 

784 "if": _keywords.if_, 

785 "items": _legacy_keywords.items_draft6_draft7_draft201909, 

786 "maxItems": _keywords.maxItems, 

787 "maxLength": _keywords.maxLength, 

788 "maxProperties": _keywords.maxProperties, 

789 "maximum": _keywords.maximum, 

790 "minItems": _keywords.minItems, 

791 "minLength": _keywords.minLength, 

792 "minProperties": _keywords.minProperties, 

793 "minimum": _keywords.minimum, 

794 "multipleOf": _keywords.multipleOf, 

795 "not": _keywords.not_, 

796 "oneOf": _keywords.oneOf, 

797 "pattern": _keywords.pattern, 

798 "patternProperties": _keywords.patternProperties, 

799 "properties": _keywords.properties, 

800 "propertyNames": _keywords.propertyNames, 

801 "required": _keywords.required, 

802 "type": _keywords.type, 

803 "unevaluatedItems": _legacy_keywords.unevaluatedItems_draft2019, 

804 "unevaluatedProperties": ( 

805 _legacy_keywords.unevaluatedProperties_draft2019 

806 ), 

807 "uniqueItems": _keywords.uniqueItems, 

808 }, 

809 type_checker=_types.draft201909_type_checker, 

810 format_checker=_format.draft201909_format_checker, 

811 version="draft2019-09", 

812) 

813 

814Draft202012Validator = create( 

815 meta_schema=SPECIFICATIONS.contents( 

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

817 ), 

818 validators={ 

819 "$dynamicRef": _keywords.dynamicRef, 

820 "$ref": _keywords.ref, 

821 "additionalProperties": _keywords.additionalProperties, 

822 "allOf": _keywords.allOf, 

823 "anyOf": _keywords.anyOf, 

824 "const": _keywords.const, 

825 "contains": _keywords.contains, 

826 "dependentRequired": _keywords.dependentRequired, 

827 "dependentSchemas": _keywords.dependentSchemas, 

828 "enum": _keywords.enum, 

829 "exclusiveMaximum": _keywords.exclusiveMaximum, 

830 "exclusiveMinimum": _keywords.exclusiveMinimum, 

831 "format": _keywords.format, 

832 "if": _keywords.if_, 

833 "items": _keywords.items, 

834 "maxItems": _keywords.maxItems, 

835 "maxLength": _keywords.maxLength, 

836 "maxProperties": _keywords.maxProperties, 

837 "maximum": _keywords.maximum, 

838 "minItems": _keywords.minItems, 

839 "minLength": _keywords.minLength, 

840 "minProperties": _keywords.minProperties, 

841 "minimum": _keywords.minimum, 

842 "multipleOf": _keywords.multipleOf, 

843 "not": _keywords.not_, 

844 "oneOf": _keywords.oneOf, 

845 "pattern": _keywords.pattern, 

846 "patternProperties": _keywords.patternProperties, 

847 "prefixItems": _keywords.prefixItems, 

848 "properties": _keywords.properties, 

849 "propertyNames": _keywords.propertyNames, 

850 "required": _keywords.required, 

851 "type": _keywords.type, 

852 "unevaluatedItems": _keywords.unevaluatedItems, 

853 "unevaluatedProperties": _keywords.unevaluatedProperties, 

854 "uniqueItems": _keywords.uniqueItems, 

855 }, 

856 type_checker=_types.draft202012_type_checker, 

857 format_checker=_format.draft202012_format_checker, 

858 version="draft2020-12", 

859) 

860 

861_LATEST_VERSION: type[Validator] = Draft202012Validator 

862 

863 

864class _RefResolver: 

865 """ 

866 Resolve JSON References. 

867 

868 Arguments: 

869 

870 base_uri (str): 

871 

872 The URI of the referring document 

873 

874 referrer: 

875 

876 The actual referring document 

877 

878 store (dict): 

879 

880 A mapping from URIs to documents to cache 

881 

882 cache_remote (bool): 

883 

884 Whether remote refs should be cached after first resolution 

885 

886 handlers (dict): 

887 

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

889 to retrieve them 

890 

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

892 

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

894 the resolution scope to subscopes. 

895 

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

897 

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

899 resolved remote URLs. 

900 

901 Attributes: 

902 

903 cache_remote (bool): 

904 

905 Whether remote refs should be cached after first resolution 

906 

907 .. deprecated:: v4.18.0 

908 

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

910 

911 """ 

912 

913 _DEPRECATION_MESSAGE = ( 

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

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

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

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

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

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

920 ) 

921 

922 def __init__( 

923 self, 

924 base_uri, 

925 referrer, 

926 store=HashTrieMap(), 

927 cache_remote=True, 

928 handlers=(), 

929 urljoin_cache=None, 

930 remote_cache=None, 

931 ): 

932 if urljoin_cache is None: 

933 urljoin_cache = lru_cache(1024)(urljoin) 

934 if remote_cache is None: 

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

936 

937 self.referrer = referrer 

938 self.cache_remote = cache_remote 

939 self.handlers = dict(handlers) 

940 

941 self._scopes_stack = [base_uri] 

942 

943 self.store = _utils.URIDict( 

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

945 ) 

946 self.store.update( 

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

948 ) 

949 self.store.update(store) 

950 self.store.update( 

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

952 for schema in store.values() 

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

954 ) 

955 self.store[base_uri] = referrer 

956 

957 self._urljoin_cache = urljoin_cache 

958 self._remote_cache = remote_cache 

959 

960 @classmethod 

961 def from_schema( # noqa: D417 

962 cls, 

963 schema, 

964 id_of=referencing.jsonschema.DRAFT202012.id_of, 

965 *args, 

966 **kwargs, 

967 ): 

968 """ 

969 Construct a resolver from a JSON schema object. 

970 

971 Arguments: 

972 

973 schema: 

974 

975 the referring schema 

976 

977 Returns: 

978 

979 `_RefResolver` 

980 

981 """ 

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

983 

984 def push_scope(self, scope): 

985 """ 

986 Enter a given sub-scope. 

987 

988 Treats further dereferences as being performed underneath the 

989 given scope. 

990 """ 

991 self._scopes_stack.append( 

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

993 ) 

994 

995 def pop_scope(self): 

996 """ 

997 Exit the most recent entered scope. 

998 

999 Treats further dereferences as being performed underneath the 

1000 original scope. 

1001 

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

1003 called. 

1004 """ 

1005 try: 

1006 self._scopes_stack.pop() 

1007 except IndexError: 

1008 raise exceptions._RefResolutionError( 

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

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

1011 "`push_scope()`", 

1012 ) from None 

1013 

1014 @property 

1015 def resolution_scope(self): 

1016 """ 

1017 Retrieve the current resolution scope. 

1018 """ 

1019 return self._scopes_stack[-1] 

1020 

1021 @property 

1022 def base_uri(self): 

1023 """ 

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

1025 """ 

1026 uri, _ = urldefrag(self.resolution_scope) 

1027 return uri 

1028 

1029 @contextlib.contextmanager 

1030 def in_scope(self, scope): 

1031 """ 

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

1033 

1034 .. deprecated:: v4.0.0 

1035 """ 

1036 warnings.warn( 

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

1038 "removed in a future release.", 

1039 DeprecationWarning, 

1040 stacklevel=3, 

1041 ) 

1042 self.push_scope(scope) 

1043 try: 

1044 yield 

1045 finally: 

1046 self.pop_scope() 

1047 

1048 @contextlib.contextmanager 

1049 def resolving(self, ref): 

1050 """ 

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

1052 

1053 Exits the scope on exit of this context manager. 

1054 

1055 Arguments: 

1056 

1057 ref (str): 

1058 

1059 The reference to resolve 

1060 

1061 """ 

1062 url, resolved = self.resolve(ref) 

1063 self.push_scope(url) 

1064 try: 

1065 yield resolved 

1066 finally: 

1067 self.pop_scope() 

1068 

1069 def _find_in_referrer(self, key): 

1070 return self._get_subschemas_cache()[key] 

1071 

1072 @lru_cache # noqa: B019 

1073 def _get_subschemas_cache(self): 

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

1075 for keyword, subschema in _search_schema( 

1076 self.referrer, _match_subschema_keywords, 

1077 ): 

1078 cache[keyword].append(subschema) 

1079 return cache 

1080 

1081 @lru_cache # noqa: B019 

1082 def _find_in_subschemas(self, url): 

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

1084 if not subschemas: 

1085 return None 

1086 uri, fragment = urldefrag(url) 

1087 for subschema in subschemas: 

1088 id = subschema["$id"] 

1089 if not isinstance(id, str): 

1090 continue 

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

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

1093 if fragment: 

1094 subschema = self.resolve_fragment(subschema, fragment) 

1095 self.store[url] = subschema 

1096 return url, subschema 

1097 return None 

1098 

1099 def resolve(self, ref): 

1100 """ 

1101 Resolve the given reference. 

1102 """ 

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

1104 

1105 match = self._find_in_subschemas(url) 

1106 if match is not None: 

1107 return match 

1108 

1109 return url, self._remote_cache(url) 

1110 

1111 def resolve_from_url(self, url): 

1112 """ 

1113 Resolve the given URL. 

1114 """ 

1115 url, fragment = urldefrag(url) 

1116 if not url: 

1117 url = self.base_uri 

1118 

1119 try: 

1120 document = self.store[url] 

1121 except KeyError: 

1122 try: 

1123 document = self.resolve_remote(url) 

1124 except Exception as exc: 

1125 raise exceptions._RefResolutionError(exc) from exc 

1126 

1127 return self.resolve_fragment(document, fragment) 

1128 

1129 def resolve_fragment(self, document, fragment): 

1130 """ 

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

1132 

1133 Arguments: 

1134 

1135 document: 

1136 

1137 The referent document 

1138 

1139 fragment (str): 

1140 

1141 a URI fragment to resolve within it 

1142 

1143 """ 

1144 fragment = fragment.lstrip("/") 

1145 

1146 if not fragment: 

1147 return document 

1148 

1149 if document is self.referrer: 

1150 find = self._find_in_referrer 

1151 else: 

1152 

1153 def find(key): 

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

1155 

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

1157 for subschema in find(keyword): 

1158 if fragment == subschema[keyword]: 

1159 return subschema 

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

1161 for subschema in find(keyword): 

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

1163 return subschema 

1164 

1165 # Resolve via path 

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

1167 for part in parts: 

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

1169 

1170 if isinstance(document, Sequence): 

1171 try: # noqa: SIM105 

1172 part = int(part) 

1173 except ValueError: 

1174 pass 

1175 try: 

1176 document = document[part] 

1177 except (TypeError, LookupError) as err: 

1178 raise exceptions._RefResolutionError( 

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

1180 ) from err 

1181 

1182 return document 

1183 

1184 def resolve_remote(self, uri): 

1185 """ 

1186 Resolve a remote ``uri``. 

1187 

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

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

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

1191 

1192 .. note:: 

1193 

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

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

1196 detected and used. 

1197 

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

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

1200 

1201 Arguments: 

1202 

1203 uri (str): 

1204 

1205 The URI to resolve 

1206 

1207 Returns: 

1208 

1209 The retrieved document 

1210 

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

1212 

1213 """ 

1214 try: 

1215 import requests 

1216 except ImportError: 

1217 requests = None 

1218 

1219 scheme = urlsplit(uri).scheme 

1220 

1221 if scheme in self.handlers: 

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

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

1224 # Requests has support for detecting the correct encoding of 

1225 # json over http 

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

1227 else: 

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

1229 from urllib.request import urlopen 

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

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

1232 

1233 if self.cache_remote: 

1234 self.store[uri] = result 

1235 return result 

1236 

1237 

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

1239 

1240 

1241def _match_keyword(keyword): 

1242 

1243 def matcher(value): 

1244 if keyword in value: 

1245 yield value 

1246 

1247 return matcher 

1248 

1249 

1250def _match_subschema_keywords(value): 

1251 for keyword in _SUBSCHEMAS_KEYWORDS: 

1252 if keyword in value: 

1253 yield keyword, value 

1254 

1255 

1256def _search_schema(schema, matcher): 

1257 """Breadth-first search routine.""" 

1258 values = deque([schema]) 

1259 while values: 

1260 value = values.pop() 

1261 if not isinstance(value, dict): 

1262 continue 

1263 yield from matcher(value) 

1264 values.extendleft(value.values()) 

1265 

1266 

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

1268 """ 

1269 Validate an instance under the given schema. 

1270 

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

1272 Traceback (most recent call last): 

1273 ... 

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

1275 

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

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

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

1279 

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

1281 if you intend to validate multiple instances with 

1282 the same schema, you likely would prefer using the 

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

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

1285 

1286 

1287 Arguments: 

1288 

1289 instance: 

1290 

1291 The instance to validate 

1292 

1293 schema: 

1294 

1295 The schema to validate with 

1296 

1297 cls (jsonschema.protocols.Validator): 

1298 

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

1300 

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

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

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

1304 proper validator will be used. The specification recommends that 

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

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

1307 latest released draft. 

1308 

1309 Any other provided positional and keyword arguments will be passed 

1310 on when instantiating the ``cls``. 

1311 

1312 Raises: 

1313 

1314 `jsonschema.exceptions.ValidationError`: 

1315 

1316 if the instance is invalid 

1317 

1318 `jsonschema.exceptions.SchemaError`: 

1319 

1320 if the schema itself is invalid 

1321 

1322 .. rubric:: Footnotes 

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

1324 `jsonschema.validators.validates` 

1325 

1326 """ 

1327 if cls is None: 

1328 cls = validator_for(schema) 

1329 

1330 cls.check_schema(schema) 

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

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

1333 if error is not None: 

1334 raise error 

1335 

1336 

1337def validator_for( 

1338 schema, 

1339 default: type[Validator] | _utils.Unset = _UNSET, 

1340) -> type[Validator]: 

1341 """ 

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

1343 

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

1345 schema to look up the appropriate validator class. 

1346 

1347 Arguments: 

1348 

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

1350 

1351 the schema to look at 

1352 

1353 default: 

1354 

1355 the default to return if the appropriate validator class 

1356 cannot be determined. 

1357 

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

1359 draft. 

1360 

1361 Examples: 

1362 

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

1364 class is returned: 

1365 

1366 >>> schema = { 

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

1368 ... "type": "integer", 

1369 ... } 

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

1371 <class 'jsonschema.validators.Draft202012Validator'> 

1372 

1373 

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

1375 

1376 >>> schema = { 

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

1378 ... "type": "integer", 

1379 ... } 

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

1381 <class 'jsonschema.validators.Draft7Validator'> 

1382 

1383 

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

1385 argument: 

1386 

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

1388 >>> jsonschema.validators.validator_for( 

1389 ... schema, default=Draft7Validator, 

1390 ... ) 

1391 <class 'jsonschema.validators.Draft7Validator'> 

1392 

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

1394 Always including the keyword when authoring schemas is highly 

1395 recommended. 

1396 

1397 """ 

1398 DefaultValidator = _LATEST_VERSION if default is _UNSET else default 

1399 

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

1401 return DefaultValidator # type: ignore[return-value] 

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

1403 warn( 

1404 ( 

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

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

1407 "an error in the future." 

1408 ), 

1409 DeprecationWarning, 

1410 stacklevel=2, 

1411 ) 

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