Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/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

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 ) 

414 return 

415 

416 if self._ref_resolver is not None: 

417 evolved = self.evolve(schema=schema) 

418 else: 

419 if resolver is None: 

420 resolver = self._resolver.in_subresource( 

421 specification.create_resource(schema), 

422 ) 

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

424 

425 for k, v in applicable_validators(schema): 

426 validator = evolved.VALIDATORS.get(k) 

427 if validator is None: 

428 continue 

429 

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

431 for error in errors: 

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

433 error._set( 

434 validator=k, 

435 validator_value=v, 

436 instance=instance, 

437 schema=schema, 

438 type_checker=evolved.TYPE_CHECKER, 

439 ) 

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

441 error.schema_path.appendleft(k) 

442 if path is not None: 

443 error.path.appendleft(path) 

444 if schema_path is not None: 

445 error.schema_path.appendleft(schema_path) 

446 yield error 

447 

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

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

450 raise error 

451 

452 def is_type(self, instance, type): 

453 try: 

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

455 except exceptions.UndefinedTypeCheck: 

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

457 raise exc from None 

458 

459 def _validate_reference(self, ref, instance): 

460 if self._ref_resolver is None: 

461 try: 

462 resolved = self._resolver.lookup(ref) 

463 except referencing.exceptions.Unresolvable as err: 

464 raise exceptions._WrappedReferencingError(err) from err 

465 

466 return self.descend( 

467 instance, 

468 resolved.contents, 

469 resolver=resolved.resolver, 

470 ) 

471 else: 

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

473 if resolve is None: 

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

475 return self.descend(instance, resolved) 

476 else: 

477 scope, resolved = resolve(ref) 

478 self._ref_resolver.push_scope(scope) 

479 

480 try: 

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

482 finally: 

483 self._ref_resolver.pop_scope() 

484 

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

486 if _schema is not None: 

487 warnings.warn( 

488 ( 

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

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

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

492 "instead." 

493 ), 

494 DeprecationWarning, 

495 stacklevel=2, 

496 ) 

497 self = self.evolve(schema=_schema) 

498 

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

500 return error is None 

501 

502 evolve_fields = [ 

503 (field.name, field.alias) 

504 for field in fields(Validator) 

505 if field.init 

506 ] 

507 

508 if version is not None: 

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

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

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

512 

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

514 

515 

516def extend( 

517 validator, 

518 validators=(), 

519 version=None, 

520 type_checker=None, 

521 format_checker=None, 

522): 

523 """ 

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

525 

526 Arguments: 

527 

528 validator (jsonschema.protocols.Validator): 

529 

530 an existing validator class 

531 

532 validators (collections.abc.Mapping): 

533 

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

535 structure is as in `create`. 

536 

537 .. note:: 

538 

539 Any validator callables with the same name as an 

540 existing one will (silently) replace the old validator 

541 callable entirely, effectively overriding any validation 

542 done in the "parent" validator class. 

543 

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

545 validator callable, delegate and call it directly in 

546 the new validator function by retrieving it using 

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

548 

549 version (str): 

550 

551 a version for the new validator class 

552 

553 type_checker (jsonschema.TypeChecker): 

554 

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

556 

557 If unprovided, the type checker of the extended 

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

559 

560 format_checker (jsonschema.FormatChecker): 

561 

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

563 

564 If unprovided, the format checker of the extended 

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

566 

567 Returns: 

568 

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

570 provided 

571 

572 .. note:: Meta Schemas 

573 

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

575 

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

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

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

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

580 old validator. 

581 

582 """ 

583 all_validators = dict(validator.VALIDATORS) 

584 all_validators.update(validators) 

585 

586 if type_checker is None: 

587 type_checker = validator.TYPE_CHECKER 

588 if format_checker is None: 

589 format_checker = validator.FORMAT_CHECKER 

590 return create( 

591 meta_schema=validator.META_SCHEMA, 

592 validators=all_validators, 

593 version=version, 

594 type_checker=type_checker, 

595 format_checker=format_checker, 

596 id_of=validator.ID_OF, 

597 applicable_validators=validator._APPLICABLE_VALIDATORS, 

598 ) 

599 

600 

601Draft3Validator = create( 

602 meta_schema=SPECIFICATIONS.contents( 

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

604 ), 

605 validators={ 

606 "$ref": _keywords.ref, 

607 "additionalItems": _legacy_keywords.additionalItems, 

608 "additionalProperties": _keywords.additionalProperties, 

609 "dependencies": _legacy_keywords.dependencies_draft3, 

610 "disallow": _legacy_keywords.disallow_draft3, 

611 "divisibleBy": _keywords.multipleOf, 

612 "enum": _keywords.enum, 

613 "extends": _legacy_keywords.extends_draft3, 

614 "format": _keywords.format, 

615 "items": _legacy_keywords.items_draft3_draft4, 

616 "maxItems": _keywords.maxItems, 

617 "maxLength": _keywords.maxLength, 

618 "maximum": _legacy_keywords.maximum_draft3_draft4, 

619 "minItems": _keywords.minItems, 

620 "minLength": _keywords.minLength, 

621 "minimum": _legacy_keywords.minimum_draft3_draft4, 

622 "pattern": _keywords.pattern, 

623 "patternProperties": _keywords.patternProperties, 

624 "properties": _legacy_keywords.properties_draft3, 

625 "type": _legacy_keywords.type_draft3, 

626 "uniqueItems": _keywords.uniqueItems, 

627 }, 

628 type_checker=_types.draft3_type_checker, 

629 format_checker=_format.draft3_format_checker, 

630 version="draft3", 

631 id_of=referencing.jsonschema.DRAFT3.id_of, 

632 applicable_validators=_legacy_keywords.ignore_ref_siblings, 

633) 

634 

635Draft4Validator = create( 

636 meta_schema=SPECIFICATIONS.contents( 

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

638 ), 

639 validators={ 

640 "$ref": _keywords.ref, 

641 "additionalItems": _legacy_keywords.additionalItems, 

642 "additionalProperties": _keywords.additionalProperties, 

643 "allOf": _keywords.allOf, 

644 "anyOf": _keywords.anyOf, 

645 "dependencies": _legacy_keywords.dependencies_draft4_draft6_draft7, 

646 "enum": _keywords.enum, 

647 "format": _keywords.format, 

648 "items": _legacy_keywords.items_draft3_draft4, 

649 "maxItems": _keywords.maxItems, 

650 "maxLength": _keywords.maxLength, 

651 "maxProperties": _keywords.maxProperties, 

652 "maximum": _legacy_keywords.maximum_draft3_draft4, 

653 "minItems": _keywords.minItems, 

654 "minLength": _keywords.minLength, 

655 "minProperties": _keywords.minProperties, 

656 "minimum": _legacy_keywords.minimum_draft3_draft4, 

657 "multipleOf": _keywords.multipleOf, 

658 "not": _keywords.not_, 

659 "oneOf": _keywords.oneOf, 

660 "pattern": _keywords.pattern, 

661 "patternProperties": _keywords.patternProperties, 

662 "properties": _keywords.properties, 

663 "required": _keywords.required, 

664 "type": _keywords.type, 

665 "uniqueItems": _keywords.uniqueItems, 

666 }, 

667 type_checker=_types.draft4_type_checker, 

668 format_checker=_format.draft4_format_checker, 

669 version="draft4", 

670 id_of=referencing.jsonschema.DRAFT4.id_of, 

671 applicable_validators=_legacy_keywords.ignore_ref_siblings, 

672) 

673 

674Draft6Validator = create( 

675 meta_schema=SPECIFICATIONS.contents( 

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

677 ), 

678 validators={ 

679 "$ref": _keywords.ref, 

680 "additionalItems": _legacy_keywords.additionalItems, 

681 "additionalProperties": _keywords.additionalProperties, 

682 "allOf": _keywords.allOf, 

683 "anyOf": _keywords.anyOf, 

684 "const": _keywords.const, 

685 "contains": _legacy_keywords.contains_draft6_draft7, 

686 "dependencies": _legacy_keywords.dependencies_draft4_draft6_draft7, 

687 "enum": _keywords.enum, 

688 "exclusiveMaximum": _keywords.exclusiveMaximum, 

689 "exclusiveMinimum": _keywords.exclusiveMinimum, 

690 "format": _keywords.format, 

691 "items": _legacy_keywords.items_draft6_draft7_draft201909, 

692 "maxItems": _keywords.maxItems, 

693 "maxLength": _keywords.maxLength, 

694 "maxProperties": _keywords.maxProperties, 

695 "maximum": _keywords.maximum, 

696 "minItems": _keywords.minItems, 

697 "minLength": _keywords.minLength, 

698 "minProperties": _keywords.minProperties, 

699 "minimum": _keywords.minimum, 

700 "multipleOf": _keywords.multipleOf, 

701 "not": _keywords.not_, 

702 "oneOf": _keywords.oneOf, 

703 "pattern": _keywords.pattern, 

704 "patternProperties": _keywords.patternProperties, 

705 "properties": _keywords.properties, 

706 "propertyNames": _keywords.propertyNames, 

707 "required": _keywords.required, 

708 "type": _keywords.type, 

709 "uniqueItems": _keywords.uniqueItems, 

710 }, 

711 type_checker=_types.draft6_type_checker, 

712 format_checker=_format.draft6_format_checker, 

713 version="draft6", 

714 id_of=referencing.jsonschema.DRAFT6.id_of, 

715 applicable_validators=_legacy_keywords.ignore_ref_siblings, 

716) 

717 

718Draft7Validator = create( 

719 meta_schema=SPECIFICATIONS.contents( 

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

721 ), 

722 validators={ 

723 "$ref": _keywords.ref, 

724 "additionalItems": _legacy_keywords.additionalItems, 

725 "additionalProperties": _keywords.additionalProperties, 

726 "allOf": _keywords.allOf, 

727 "anyOf": _keywords.anyOf, 

728 "const": _keywords.const, 

729 "contains": _legacy_keywords.contains_draft6_draft7, 

730 "dependencies": _legacy_keywords.dependencies_draft4_draft6_draft7, 

731 "enum": _keywords.enum, 

732 "exclusiveMaximum": _keywords.exclusiveMaximum, 

733 "exclusiveMinimum": _keywords.exclusiveMinimum, 

734 "format": _keywords.format, 

735 "if": _keywords.if_, 

736 "items": _legacy_keywords.items_draft6_draft7_draft201909, 

737 "maxItems": _keywords.maxItems, 

738 "maxLength": _keywords.maxLength, 

739 "maxProperties": _keywords.maxProperties, 

740 "maximum": _keywords.maximum, 

741 "minItems": _keywords.minItems, 

742 "minLength": _keywords.minLength, 

743 "minProperties": _keywords.minProperties, 

744 "minimum": _keywords.minimum, 

745 "multipleOf": _keywords.multipleOf, 

746 "not": _keywords.not_, 

747 "oneOf": _keywords.oneOf, 

748 "pattern": _keywords.pattern, 

749 "patternProperties": _keywords.patternProperties, 

750 "properties": _keywords.properties, 

751 "propertyNames": _keywords.propertyNames, 

752 "required": _keywords.required, 

753 "type": _keywords.type, 

754 "uniqueItems": _keywords.uniqueItems, 

755 }, 

756 type_checker=_types.draft7_type_checker, 

757 format_checker=_format.draft7_format_checker, 

758 version="draft7", 

759 id_of=referencing.jsonschema.DRAFT7.id_of, 

760 applicable_validators=_legacy_keywords.ignore_ref_siblings, 

761) 

762 

763Draft201909Validator = create( 

764 meta_schema=SPECIFICATIONS.contents( 

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

766 ), 

767 validators={ 

768 "$recursiveRef": _legacy_keywords.recursiveRef, 

769 "$ref": _keywords.ref, 

770 "additionalItems": _legacy_keywords.additionalItems, 

771 "additionalProperties": _keywords.additionalProperties, 

772 "allOf": _keywords.allOf, 

773 "anyOf": _keywords.anyOf, 

774 "const": _keywords.const, 

775 "contains": _keywords.contains, 

776 "dependentRequired": _keywords.dependentRequired, 

777 "dependentSchemas": _keywords.dependentSchemas, 

778 "enum": _keywords.enum, 

779 "exclusiveMaximum": _keywords.exclusiveMaximum, 

780 "exclusiveMinimum": _keywords.exclusiveMinimum, 

781 "format": _keywords.format, 

782 "if": _keywords.if_, 

783 "items": _legacy_keywords.items_draft6_draft7_draft201909, 

784 "maxItems": _keywords.maxItems, 

785 "maxLength": _keywords.maxLength, 

786 "maxProperties": _keywords.maxProperties, 

787 "maximum": _keywords.maximum, 

788 "minItems": _keywords.minItems, 

789 "minLength": _keywords.minLength, 

790 "minProperties": _keywords.minProperties, 

791 "minimum": _keywords.minimum, 

792 "multipleOf": _keywords.multipleOf, 

793 "not": _keywords.not_, 

794 "oneOf": _keywords.oneOf, 

795 "pattern": _keywords.pattern, 

796 "patternProperties": _keywords.patternProperties, 

797 "properties": _keywords.properties, 

798 "propertyNames": _keywords.propertyNames, 

799 "required": _keywords.required, 

800 "type": _keywords.type, 

801 "unevaluatedItems": _legacy_keywords.unevaluatedItems_draft2019, 

802 "unevaluatedProperties": ( 

803 _legacy_keywords.unevaluatedProperties_draft2019 

804 ), 

805 "uniqueItems": _keywords.uniqueItems, 

806 }, 

807 type_checker=_types.draft201909_type_checker, 

808 format_checker=_format.draft201909_format_checker, 

809 version="draft2019-09", 

810) 

811 

812Draft202012Validator = create( 

813 meta_schema=SPECIFICATIONS.contents( 

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

815 ), 

816 validators={ 

817 "$dynamicRef": _keywords.dynamicRef, 

818 "$ref": _keywords.ref, 

819 "additionalProperties": _keywords.additionalProperties, 

820 "allOf": _keywords.allOf, 

821 "anyOf": _keywords.anyOf, 

822 "const": _keywords.const, 

823 "contains": _keywords.contains, 

824 "dependentRequired": _keywords.dependentRequired, 

825 "dependentSchemas": _keywords.dependentSchemas, 

826 "enum": _keywords.enum, 

827 "exclusiveMaximum": _keywords.exclusiveMaximum, 

828 "exclusiveMinimum": _keywords.exclusiveMinimum, 

829 "format": _keywords.format, 

830 "if": _keywords.if_, 

831 "items": _keywords.items, 

832 "maxItems": _keywords.maxItems, 

833 "maxLength": _keywords.maxLength, 

834 "maxProperties": _keywords.maxProperties, 

835 "maximum": _keywords.maximum, 

836 "minItems": _keywords.minItems, 

837 "minLength": _keywords.minLength, 

838 "minProperties": _keywords.minProperties, 

839 "minimum": _keywords.minimum, 

840 "multipleOf": _keywords.multipleOf, 

841 "not": _keywords.not_, 

842 "oneOf": _keywords.oneOf, 

843 "pattern": _keywords.pattern, 

844 "patternProperties": _keywords.patternProperties, 

845 "prefixItems": _keywords.prefixItems, 

846 "properties": _keywords.properties, 

847 "propertyNames": _keywords.propertyNames, 

848 "required": _keywords.required, 

849 "type": _keywords.type, 

850 "unevaluatedItems": _keywords.unevaluatedItems, 

851 "unevaluatedProperties": _keywords.unevaluatedProperties, 

852 "uniqueItems": _keywords.uniqueItems, 

853 }, 

854 type_checker=_types.draft202012_type_checker, 

855 format_checker=_format.draft202012_format_checker, 

856 version="draft2020-12", 

857) 

858 

859_LATEST_VERSION: type[Validator] = Draft202012Validator 

860 

861 

862class _RefResolver: 

863 """ 

864 Resolve JSON References. 

865 

866 Arguments: 

867 

868 base_uri (str): 

869 

870 The URI of the referring document 

871 

872 referrer: 

873 

874 The actual referring document 

875 

876 store (dict): 

877 

878 A mapping from URIs to documents to cache 

879 

880 cache_remote (bool): 

881 

882 Whether remote refs should be cached after first resolution 

883 

884 handlers (dict): 

885 

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

887 to retrieve them 

888 

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

890 

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

892 the resolution scope to subscopes. 

893 

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

895 

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

897 resolved remote URLs. 

898 

899 Attributes: 

900 

901 cache_remote (bool): 

902 

903 Whether remote refs should be cached after first resolution 

904 

905 .. deprecated:: v4.18.0 

906 

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

908 

909 """ 

910 

911 _DEPRECATION_MESSAGE = ( 

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

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

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

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

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

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

918 ) 

919 

920 def __init__( 

921 self, 

922 base_uri, 

923 referrer, 

924 store=HashTrieMap(), 

925 cache_remote=True, 

926 handlers=(), 

927 urljoin_cache=None, 

928 remote_cache=None, 

929 ): 

930 if urljoin_cache is None: 

931 urljoin_cache = lru_cache(1024)(urljoin) 

932 if remote_cache is None: 

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

934 

935 self.referrer = referrer 

936 self.cache_remote = cache_remote 

937 self.handlers = dict(handlers) 

938 

939 self._scopes_stack = [base_uri] 

940 

941 self.store = _utils.URIDict( 

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

943 ) 

944 self.store.update( 

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

946 ) 

947 self.store.update(store) 

948 self.store.update( 

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

950 for schema in store.values() 

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

952 ) 

953 self.store[base_uri] = referrer 

954 

955 self._urljoin_cache = urljoin_cache 

956 self._remote_cache = remote_cache 

957 

958 @classmethod 

959 def from_schema( # noqa: D417 

960 cls, 

961 schema, 

962 id_of=referencing.jsonschema.DRAFT202012.id_of, 

963 *args, 

964 **kwargs, 

965 ): 

966 """ 

967 Construct a resolver from a JSON schema object. 

968 

969 Arguments: 

970 

971 schema: 

972 

973 the referring schema 

974 

975 Returns: 

976 

977 `_RefResolver` 

978 

979 """ 

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

981 

982 def push_scope(self, scope): 

983 """ 

984 Enter a given sub-scope. 

985 

986 Treats further dereferences as being performed underneath the 

987 given scope. 

988 """ 

989 self._scopes_stack.append( 

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

991 ) 

992 

993 def pop_scope(self): 

994 """ 

995 Exit the most recent entered scope. 

996 

997 Treats further dereferences as being performed underneath the 

998 original scope. 

999 

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

1001 called. 

1002 """ 

1003 try: 

1004 self._scopes_stack.pop() 

1005 except IndexError: 

1006 raise exceptions._RefResolutionError( 

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

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

1009 "`push_scope()`", 

1010 ) from None 

1011 

1012 @property 

1013 def resolution_scope(self): 

1014 """ 

1015 Retrieve the current resolution scope. 

1016 """ 

1017 return self._scopes_stack[-1] 

1018 

1019 @property 

1020 def base_uri(self): 

1021 """ 

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

1023 """ 

1024 uri, _ = urldefrag(self.resolution_scope) 

1025 return uri 

1026 

1027 @contextlib.contextmanager 

1028 def in_scope(self, scope): 

1029 """ 

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

1031 

1032 .. deprecated:: v4.0.0 

1033 """ 

1034 warnings.warn( 

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

1036 "removed in a future release.", 

1037 DeprecationWarning, 

1038 stacklevel=3, 

1039 ) 

1040 self.push_scope(scope) 

1041 try: 

1042 yield 

1043 finally: 

1044 self.pop_scope() 

1045 

1046 @contextlib.contextmanager 

1047 def resolving(self, ref): 

1048 """ 

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

1050 

1051 Exits the scope on exit of this context manager. 

1052 

1053 Arguments: 

1054 

1055 ref (str): 

1056 

1057 The reference to resolve 

1058 

1059 """ 

1060 url, resolved = self.resolve(ref) 

1061 self.push_scope(url) 

1062 try: 

1063 yield resolved 

1064 finally: 

1065 self.pop_scope() 

1066 

1067 def _find_in_referrer(self, key): 

1068 return self._get_subschemas_cache()[key] 

1069 

1070 @lru_cache # noqa: B019 

1071 def _get_subschemas_cache(self): 

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

1073 for keyword, subschema in _search_schema( 

1074 self.referrer, _match_subschema_keywords, 

1075 ): 

1076 cache[keyword].append(subschema) 

1077 return cache 

1078 

1079 @lru_cache # noqa: B019 

1080 def _find_in_subschemas(self, url): 

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

1082 if not subschemas: 

1083 return None 

1084 uri, fragment = urldefrag(url) 

1085 for subschema in subschemas: 

1086 id = subschema["$id"] 

1087 if not isinstance(id, str): 

1088 continue 

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

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

1091 if fragment: 

1092 subschema = self.resolve_fragment(subschema, fragment) 

1093 self.store[url] = subschema 

1094 return url, subschema 

1095 return None 

1096 

1097 def resolve(self, ref): 

1098 """ 

1099 Resolve the given reference. 

1100 """ 

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

1102 

1103 match = self._find_in_subschemas(url) 

1104 if match is not None: 

1105 return match 

1106 

1107 return url, self._remote_cache(url) 

1108 

1109 def resolve_from_url(self, url): 

1110 """ 

1111 Resolve the given URL. 

1112 """ 

1113 url, fragment = urldefrag(url) 

1114 if not url: 

1115 url = self.base_uri 

1116 

1117 try: 

1118 document = self.store[url] 

1119 except KeyError: 

1120 try: 

1121 document = self.resolve_remote(url) 

1122 except Exception as exc: 

1123 raise exceptions._RefResolutionError(exc) from exc 

1124 

1125 return self.resolve_fragment(document, fragment) 

1126 

1127 def resolve_fragment(self, document, fragment): 

1128 """ 

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

1130 

1131 Arguments: 

1132 

1133 document: 

1134 

1135 The referent document 

1136 

1137 fragment (str): 

1138 

1139 a URI fragment to resolve within it 

1140 

1141 """ 

1142 fragment = fragment.lstrip("/") 

1143 

1144 if not fragment: 

1145 return document 

1146 

1147 if document is self.referrer: 

1148 find = self._find_in_referrer 

1149 else: 

1150 

1151 def find(key): 

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

1153 

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

1155 for subschema in find(keyword): 

1156 if fragment == subschema[keyword]: 

1157 return subschema 

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

1159 for subschema in find(keyword): 

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

1161 return subschema 

1162 

1163 # Resolve via path 

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

1165 for part in parts: 

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

1167 

1168 if isinstance(document, Sequence): 

1169 try: # noqa: SIM105 

1170 part = int(part) 

1171 except ValueError: 

1172 pass 

1173 try: 

1174 document = document[part] 

1175 except (TypeError, LookupError) as err: 

1176 raise exceptions._RefResolutionError( 

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

1178 ) from err 

1179 

1180 return document 

1181 

1182 def resolve_remote(self, uri): 

1183 """ 

1184 Resolve a remote ``uri``. 

1185 

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

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

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

1189 

1190 .. note:: 

1191 

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

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

1194 detected and used. 

1195 

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

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

1198 

1199 Arguments: 

1200 

1201 uri (str): 

1202 

1203 The URI to resolve 

1204 

1205 Returns: 

1206 

1207 The retrieved document 

1208 

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

1210 

1211 """ 

1212 try: 

1213 import requests 

1214 except ImportError: 

1215 requests = None 

1216 

1217 scheme = urlsplit(uri).scheme 

1218 

1219 if scheme in self.handlers: 

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

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

1222 # Requests has support for detecting the correct encoding of 

1223 # json over http 

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

1225 else: 

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

1227 from urllib.request import urlopen 

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: type[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 # type: ignore[return-value] 

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)