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

380 statements  

« prev     ^ index     » next       coverage.py v7.3.2, created at 2023-12-08 06:51 +0000

1""" 

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

3""" 

4from __future__ import annotations 

5 

6from collections import deque 

7from collections.abc import Iterable, Mapping, Sequence 

8from functools import lru_cache 

9from operator import methodcaller 

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

11from urllib.request import urlopen 

12from warnings import warn 

13import contextlib 

14import json 

15import reprlib 

16import warnings 

17 

18from attrs import define, field, fields 

19from jsonschema_specifications import REGISTRY as SPECIFICATIONS 

20from rpds import HashTrieMap 

21import referencing.exceptions 

22import referencing.jsonschema 

23 

24from jsonschema import ( 

25 _format, 

26 _keywords, 

27 _legacy_keywords, 

28 _types, 

29 _typing, 

30 _utils, 

31 exceptions, 

32) 

33from jsonschema.protocols import Validator 

34 

35_UNSET = _utils.Unset() 

36 

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

38_META_SCHEMAS = _utils.URIDict() 

39 

40 

41def __getattr__(name): 

42 if name == "ErrorTree": 

43 warnings.warn( 

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

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

46 DeprecationWarning, 

47 stacklevel=2, 

48 ) 

49 from jsonschema.exceptions import ErrorTree 

50 return ErrorTree 

51 elif name == "validators": 

52 warnings.warn( 

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

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

55 DeprecationWarning, 

56 stacklevel=2, 

57 ) 

58 return _VALIDATORS 

59 elif name == "meta_schemas": 

60 warnings.warn( 

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

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

63 DeprecationWarning, 

64 stacklevel=2, 

65 ) 

66 return _META_SCHEMAS 

67 elif name == "RefResolver": 

68 warnings.warn( 

69 _RefResolver._DEPRECATION_MESSAGE, 

70 DeprecationWarning, 

71 stacklevel=2, 

72 ) 

73 return _RefResolver 

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

75 

76 

77def validates(version): 

78 """ 

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

80 

81 Registered validators and their meta schemas will be considered when 

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

83 

84 Arguments: 

85 

86 version (str): 

87 

88 An identifier to use as the version's name 

89 

90 Returns: 

91 

92 collections.abc.Callable: 

93 

94 a class decorator to decorate the validator with the version 

95 """ 

96 

97 def _validates(cls): 

98 _VALIDATORS[version] = cls 

99 meta_schema_id = cls.ID_OF(cls.META_SCHEMA) 

100 _META_SCHEMAS[meta_schema_id] = cls 

101 return cls 

102 return _validates 

103 

104 

105def _warn_for_remote_retrieve(uri: str): 

106 from urllib.request import Request, urlopen 

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

108 request = Request(uri, headers=headers) 

109 with urlopen(request) as response: 

110 warnings.warn( 

111 "Automatically retrieving remote references can be a security " 

112 "vulnerability and is discouraged by the JSON Schema " 

113 "specifications. Relying on this behavior is deprecated " 

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

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

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

117 "in the referencing documentation " 

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

119 DeprecationWarning, 

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

121 ) 

122 return referencing.Resource.from_contents( 

123 json.load(response), 

124 default_specification=referencing.jsonschema.DRAFT202012, 

125 ) 

126 

127 

128_REMOTE_WARNING_REGISTRY = SPECIFICATIONS.combine( 

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

130) 

131 

132 

133def create( 

134 meta_schema: referencing.jsonschema.ObjectSchema, 

135 validators: ( 

136 Mapping[str, _typing.SchemaKeywordValidator] 

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

138 ) = (), 

139 version: str | None = None, 

140 type_checker: _types.TypeChecker = _types.draft202012_type_checker, 

141 format_checker: _format.FormatChecker = _format.draft202012_format_checker, 

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

143 applicable_validators: _typing.ApplicableValidators = methodcaller( 

144 "items", 

145 ), 

146): 

147 """ 

148 Create a new validator class. 

149 

150 Arguments: 

151 

152 meta_schema: 

153 

154 the meta schema for the new validator class 

155 

156 validators: 

157 

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

159 validate the schema property with the given name. 

160 

161 Each callable should take 4 arguments: 

162 

163 1. a validator instance, 

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

165 instance 

166 3. the instance 

167 4. the schema 

168 

169 version: 

170 

171 an identifier for the version that this validator class will 

172 validate. If provided, the returned validator class will 

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

174 will have `jsonschema.validators.validates` automatically 

175 called for the given version. 

176 

177 type_checker: 

178 

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

180 

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

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

183 

184 format_checker: 

185 

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

187 

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

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

190 

191 id_of: 

192 

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

194 

195 applicable_validators: 

196 

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

198 applicable schema keywords and associated values 

199 which will be used to validate the instance. 

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

201 which specified behavior around ignoring keywords if they were 

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

203 implement similar behavior, you can typically ignore this argument 

204 and leave it at its default. 

205 

206 Returns: 

207 

208 a new `jsonschema.protocols.Validator` class 

209 """ 

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

211 format_checker_arg = format_checker 

212 

213 specification = referencing.jsonschema.specification_with( 

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

215 default=referencing.Specification.OPAQUE, 

216 ) 

217 

218 @define 

219 class Validator: 

220 

221 VALIDATORS = dict(validators) # noqa: RUF012 

222 META_SCHEMA = dict(meta_schema) # noqa: RUF012 

223 TYPE_CHECKER = type_checker 

224 FORMAT_CHECKER = format_checker_arg 

225 ID_OF = staticmethod(id_of) 

226 

227 _APPLICABLE_VALIDATORS = applicable_validators 

228 

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

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

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

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

233 _registry: referencing.jsonschema.SchemaRegistry = field( 

234 default=_REMOTE_WARNING_REGISTRY, 

235 kw_only=True, 

236 repr=False, 

237 ) 

238 _resolver = field( 

239 alias="_resolver", 

240 default=None, 

241 kw_only=True, 

242 repr=False, 

243 ) 

244 

245 def __init_subclass__(cls): 

246 warnings.warn( 

247 ( 

248 "Subclassing validator classes is not intended to " 

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

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

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

252 "between releases of jsonschema. Instead, prefer " 

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

254 "owned entirely by the downstream library." 

255 ), 

256 DeprecationWarning, 

257 stacklevel=2, 

258 ) 

259 

260 def evolve(self, **changes): 

261 cls = self.__class__ 

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

263 NewValidator = validator_for(schema, default=cls) 

264 

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

266 if not field.init: 

267 continue 

268 attr_name = field.name 

269 init_name = field.alias 

270 if init_name not in changes: 

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

272 

273 return NewValidator(**changes) 

274 

275 cls.evolve = evolve 

276 

277 def __attrs_post_init__(self): 

278 if self._resolver is None: 

279 registry = self._registry 

280 if registry is not _REMOTE_WARNING_REGISTRY: 

281 registry = SPECIFICATIONS.combine(registry) 

282 resource = specification.create_resource(self.schema) 

283 self._resolver = registry.resolver_with_root(resource) 

284 

285 # REMOVEME: Legacy ref resolution state management. 

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

287 if push_scope is not None: 

288 id = id_of(self.schema) 

289 if id is not None: 

290 push_scope(id) 

291 

292 @classmethod 

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

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

295 if format_checker is _UNSET: 

296 format_checker = Validator.FORMAT_CHECKER 

297 validator = Validator( 

298 schema=cls.META_SCHEMA, 

299 format_checker=format_checker, 

300 ) 

301 for error in validator.iter_errors(schema): 

302 raise exceptions.SchemaError.create_from(error) 

303 

304 @property 

305 def resolver(self): 

306 warnings.warn( 

307 ( 

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

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

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

311 "library, which provides more compliant referencing " 

312 "behavior as well as more flexible APIs for " 

313 "customization." 

314 ), 

315 DeprecationWarning, 

316 stacklevel=2, 

317 ) 

318 if self._ref_resolver is None: 

319 self._ref_resolver = _RefResolver.from_schema( 

320 self.schema, 

321 id_of=id_of, 

322 ) 

323 return self._ref_resolver 

324 

325 def evolve(self, **changes): 

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

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

328 

329 for (attr_name, init_name) in evolve_fields: 

330 if init_name not in changes: 

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

332 

333 return NewValidator(**changes) 

334 

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

336 if _schema is not None: 

337 warnings.warn( 

338 ( 

339 "Passing a schema to Validator.iter_errors " 

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

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

342 "iter_errors(...) instead." 

343 ), 

344 DeprecationWarning, 

345 stacklevel=2, 

346 ) 

347 else: 

348 _schema = self.schema 

349 

350 if _schema is True: 

351 return 

352 elif _schema is False: 

353 yield exceptions.ValidationError( 

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

355 validator=None, 

356 validator_value=None, 

357 instance=instance, 

358 schema=_schema, 

359 ) 

360 return 

361 

362 for k, v in applicable_validators(_schema): 

363 validator = self.VALIDATORS.get(k) 

364 if validator is None: 

365 continue 

366 

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

368 for error in errors: 

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

370 error._set( 

371 validator=k, 

372 validator_value=v, 

373 instance=instance, 

374 schema=_schema, 

375 type_checker=self.TYPE_CHECKER, 

376 ) 

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

378 error.schema_path.appendleft(k) 

379 yield error 

380 

381 def descend( 

382 self, 

383 instance, 

384 schema, 

385 path=None, 

386 schema_path=None, 

387 resolver=None, 

388 ): 

389 if schema is True: 

390 return 

391 elif schema is False: 

392 yield exceptions.ValidationError( 

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

394 validator=None, 

395 validator_value=None, 

396 instance=instance, 

397 schema=schema, 

398 ) 

399 return 

400 

401 if self._ref_resolver is not None: 

402 evolved = self.evolve(schema=schema) 

403 else: 

404 if resolver is None: 

405 resolver = self._resolver.in_subresource( 

406 specification.create_resource(schema), 

407 ) 

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

409 

410 for k, v in applicable_validators(schema): 

411 validator = evolved.VALIDATORS.get(k) 

412 if validator is None: 

413 continue 

414 

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

416 for error in errors: 

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

418 error._set( 

419 validator=k, 

420 validator_value=v, 

421 instance=instance, 

422 schema=schema, 

423 type_checker=evolved.TYPE_CHECKER, 

424 ) 

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

426 error.schema_path.appendleft(k) 

427 if path is not None: 

428 error.path.appendleft(path) 

429 if schema_path is not None: 

430 error.schema_path.appendleft(schema_path) 

431 yield error 

432 

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

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

435 raise error 

436 

437 def is_type(self, instance, type): 

438 try: 

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

440 except exceptions.UndefinedTypeCheck: 

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

442 

443 def _validate_reference(self, ref, instance): 

444 if self._ref_resolver is None: 

445 try: 

446 resolved = self._resolver.lookup(ref) 

447 except referencing.exceptions.Unresolvable as err: 

448 raise exceptions._WrappedReferencingError(err) 

449 

450 return self.descend( 

451 instance, 

452 resolved.contents, 

453 resolver=resolved.resolver, 

454 ) 

455 else: 

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

457 if resolve is None: 

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

459 return self.descend(instance, resolved) 

460 else: 

461 scope, resolved = resolve(ref) 

462 self._ref_resolver.push_scope(scope) 

463 

464 try: 

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

466 finally: 

467 self._ref_resolver.pop_scope() 

468 

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

470 if _schema is not None: 

471 warnings.warn( 

472 ( 

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

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

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

476 "instead." 

477 ), 

478 DeprecationWarning, 

479 stacklevel=2, 

480 ) 

481 self = self.evolve(schema=_schema) 

482 

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

484 return error is None 

485 

486 evolve_fields = [ 

487 (field.name, field.alias) 

488 for field in fields(Validator) 

489 if field.init 

490 ] 

491 

492 if version is not None: 

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

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

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

496 

497 return Validator 

498 

499 

500def extend( 

501 validator, 

502 validators=(), 

503 version=None, 

504 type_checker=None, 

505 format_checker=None, 

506): 

507 """ 

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

509 

510 Arguments: 

511 

512 validator (jsonschema.protocols.Validator): 

513 

514 an existing validator class 

515 

516 validators (collections.abc.Mapping): 

517 

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

519 structure is as in `create`. 

520 

521 .. note:: 

522 

523 Any validator callables with the same name as an 

524 existing one will (silently) replace the old validator 

525 callable entirely, effectively overriding any validation 

526 done in the "parent" validator class. 

527 

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

529 validator callable, delegate and call it directly in 

530 the new validator function by retrieving it using 

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

532 

533 version (str): 

534 

535 a version for the new validator class 

536 

537 type_checker (jsonschema.TypeChecker): 

538 

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

540 

541 If unprovided, the type checker of the extended 

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

543 

544 format_checker (jsonschema.FormatChecker): 

545 

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

547 

548 If unprovided, the format checker of the extended 

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

550 

551 Returns: 

552 

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

554 provided 

555 

556 .. note:: Meta Schemas 

557 

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

559 

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

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

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

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

564 old validator. 

565 """ 

566 all_validators = dict(validator.VALIDATORS) 

567 all_validators.update(validators) 

568 

569 if type_checker is None: 

570 type_checker = validator.TYPE_CHECKER 

571 if format_checker is None: 

572 format_checker = validator.FORMAT_CHECKER 

573 return create( 

574 meta_schema=validator.META_SCHEMA, 

575 validators=all_validators, 

576 version=version, 

577 type_checker=type_checker, 

578 format_checker=format_checker, 

579 id_of=validator.ID_OF, 

580 applicable_validators=validator._APPLICABLE_VALIDATORS, 

581 ) 

582 

583 

584Draft3Validator = create( 

585 meta_schema=SPECIFICATIONS.contents( 

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

587 ), 

588 validators={ 

589 "$ref": _keywords.ref, 

590 "additionalItems": _legacy_keywords.additionalItems, 

591 "additionalProperties": _keywords.additionalProperties, 

592 "dependencies": _legacy_keywords.dependencies_draft3, 

593 "disallow": _legacy_keywords.disallow_draft3, 

594 "divisibleBy": _keywords.multipleOf, 

595 "enum": _keywords.enum, 

596 "extends": _legacy_keywords.extends_draft3, 

597 "format": _keywords.format, 

598 "items": _legacy_keywords.items_draft3_draft4, 

599 "maxItems": _keywords.maxItems, 

600 "maxLength": _keywords.maxLength, 

601 "maximum": _legacy_keywords.maximum_draft3_draft4, 

602 "minItems": _keywords.minItems, 

603 "minLength": _keywords.minLength, 

604 "minimum": _legacy_keywords.minimum_draft3_draft4, 

605 "pattern": _keywords.pattern, 

606 "patternProperties": _keywords.patternProperties, 

607 "properties": _legacy_keywords.properties_draft3, 

608 "type": _legacy_keywords.type_draft3, 

609 "uniqueItems": _keywords.uniqueItems, 

610 }, 

611 type_checker=_types.draft3_type_checker, 

612 format_checker=_format.draft3_format_checker, 

613 version="draft3", 

614 id_of=referencing.jsonschema.DRAFT3.id_of, 

615 applicable_validators=_legacy_keywords.ignore_ref_siblings, 

616) 

617 

618Draft4Validator = create( 

619 meta_schema=SPECIFICATIONS.contents( 

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

621 ), 

622 validators={ 

623 "$ref": _keywords.ref, 

624 "additionalItems": _legacy_keywords.additionalItems, 

625 "additionalProperties": _keywords.additionalProperties, 

626 "allOf": _keywords.allOf, 

627 "anyOf": _keywords.anyOf, 

628 "dependencies": _legacy_keywords.dependencies_draft4_draft6_draft7, 

629 "enum": _keywords.enum, 

630 "format": _keywords.format, 

631 "items": _legacy_keywords.items_draft3_draft4, 

632 "maxItems": _keywords.maxItems, 

633 "maxLength": _keywords.maxLength, 

634 "maxProperties": _keywords.maxProperties, 

635 "maximum": _legacy_keywords.maximum_draft3_draft4, 

636 "minItems": _keywords.minItems, 

637 "minLength": _keywords.minLength, 

638 "minProperties": _keywords.minProperties, 

639 "minimum": _legacy_keywords.minimum_draft3_draft4, 

640 "multipleOf": _keywords.multipleOf, 

641 "not": _keywords.not_, 

642 "oneOf": _keywords.oneOf, 

643 "pattern": _keywords.pattern, 

644 "patternProperties": _keywords.patternProperties, 

645 "properties": _keywords.properties, 

646 "required": _keywords.required, 

647 "type": _keywords.type, 

648 "uniqueItems": _keywords.uniqueItems, 

649 }, 

650 type_checker=_types.draft4_type_checker, 

651 format_checker=_format.draft4_format_checker, 

652 version="draft4", 

653 id_of=referencing.jsonschema.DRAFT4.id_of, 

654 applicable_validators=_legacy_keywords.ignore_ref_siblings, 

655) 

656 

657Draft6Validator = create( 

658 meta_schema=SPECIFICATIONS.contents( 

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

660 ), 

661 validators={ 

662 "$ref": _keywords.ref, 

663 "additionalItems": _legacy_keywords.additionalItems, 

664 "additionalProperties": _keywords.additionalProperties, 

665 "allOf": _keywords.allOf, 

666 "anyOf": _keywords.anyOf, 

667 "const": _keywords.const, 

668 "contains": _legacy_keywords.contains_draft6_draft7, 

669 "dependencies": _legacy_keywords.dependencies_draft4_draft6_draft7, 

670 "enum": _keywords.enum, 

671 "exclusiveMaximum": _keywords.exclusiveMaximum, 

672 "exclusiveMinimum": _keywords.exclusiveMinimum, 

673 "format": _keywords.format, 

674 "items": _legacy_keywords.items_draft6_draft7_draft201909, 

675 "maxItems": _keywords.maxItems, 

676 "maxLength": _keywords.maxLength, 

677 "maxProperties": _keywords.maxProperties, 

678 "maximum": _keywords.maximum, 

679 "minItems": _keywords.minItems, 

680 "minLength": _keywords.minLength, 

681 "minProperties": _keywords.minProperties, 

682 "minimum": _keywords.minimum, 

683 "multipleOf": _keywords.multipleOf, 

684 "not": _keywords.not_, 

685 "oneOf": _keywords.oneOf, 

686 "pattern": _keywords.pattern, 

687 "patternProperties": _keywords.patternProperties, 

688 "properties": _keywords.properties, 

689 "propertyNames": _keywords.propertyNames, 

690 "required": _keywords.required, 

691 "type": _keywords.type, 

692 "uniqueItems": _keywords.uniqueItems, 

693 }, 

694 type_checker=_types.draft6_type_checker, 

695 format_checker=_format.draft6_format_checker, 

696 version="draft6", 

697 id_of=referencing.jsonschema.DRAFT6.id_of, 

698 applicable_validators=_legacy_keywords.ignore_ref_siblings, 

699) 

700 

701Draft7Validator = create( 

702 meta_schema=SPECIFICATIONS.contents( 

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

704 ), 

705 validators={ 

706 "$ref": _keywords.ref, 

707 "additionalItems": _legacy_keywords.additionalItems, 

708 "additionalProperties": _keywords.additionalProperties, 

709 "allOf": _keywords.allOf, 

710 "anyOf": _keywords.anyOf, 

711 "const": _keywords.const, 

712 "contains": _legacy_keywords.contains_draft6_draft7, 

713 "dependencies": _legacy_keywords.dependencies_draft4_draft6_draft7, 

714 "enum": _keywords.enum, 

715 "exclusiveMaximum": _keywords.exclusiveMaximum, 

716 "exclusiveMinimum": _keywords.exclusiveMinimum, 

717 "format": _keywords.format, 

718 "if": _keywords.if_, 

719 "items": _legacy_keywords.items_draft6_draft7_draft201909, 

720 "maxItems": _keywords.maxItems, 

721 "maxLength": _keywords.maxLength, 

722 "maxProperties": _keywords.maxProperties, 

723 "maximum": _keywords.maximum, 

724 "minItems": _keywords.minItems, 

725 "minLength": _keywords.minLength, 

726 "minProperties": _keywords.minProperties, 

727 "minimum": _keywords.minimum, 

728 "multipleOf": _keywords.multipleOf, 

729 "not": _keywords.not_, 

730 "oneOf": _keywords.oneOf, 

731 "pattern": _keywords.pattern, 

732 "patternProperties": _keywords.patternProperties, 

733 "properties": _keywords.properties, 

734 "propertyNames": _keywords.propertyNames, 

735 "required": _keywords.required, 

736 "type": _keywords.type, 

737 "uniqueItems": _keywords.uniqueItems, 

738 }, 

739 type_checker=_types.draft7_type_checker, 

740 format_checker=_format.draft7_format_checker, 

741 version="draft7", 

742 id_of=referencing.jsonschema.DRAFT7.id_of, 

743 applicable_validators=_legacy_keywords.ignore_ref_siblings, 

744) 

745 

746Draft201909Validator = create( 

747 meta_schema=SPECIFICATIONS.contents( 

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

749 ), 

750 validators={ 

751 "$recursiveRef": _legacy_keywords.recursiveRef, 

752 "$ref": _keywords.ref, 

753 "additionalItems": _legacy_keywords.additionalItems, 

754 "additionalProperties": _keywords.additionalProperties, 

755 "allOf": _keywords.allOf, 

756 "anyOf": _keywords.anyOf, 

757 "const": _keywords.const, 

758 "contains": _keywords.contains, 

759 "dependentRequired": _keywords.dependentRequired, 

760 "dependentSchemas": _keywords.dependentSchemas, 

761 "enum": _keywords.enum, 

762 "exclusiveMaximum": _keywords.exclusiveMaximum, 

763 "exclusiveMinimum": _keywords.exclusiveMinimum, 

764 "format": _keywords.format, 

765 "if": _keywords.if_, 

766 "items": _legacy_keywords.items_draft6_draft7_draft201909, 

767 "maxItems": _keywords.maxItems, 

768 "maxLength": _keywords.maxLength, 

769 "maxProperties": _keywords.maxProperties, 

770 "maximum": _keywords.maximum, 

771 "minItems": _keywords.minItems, 

772 "minLength": _keywords.minLength, 

773 "minProperties": _keywords.minProperties, 

774 "minimum": _keywords.minimum, 

775 "multipleOf": _keywords.multipleOf, 

776 "not": _keywords.not_, 

777 "oneOf": _keywords.oneOf, 

778 "pattern": _keywords.pattern, 

779 "patternProperties": _keywords.patternProperties, 

780 "properties": _keywords.properties, 

781 "propertyNames": _keywords.propertyNames, 

782 "required": _keywords.required, 

783 "type": _keywords.type, 

784 "unevaluatedItems": _legacy_keywords.unevaluatedItems_draft2019, 

785 "unevaluatedProperties": ( 

786 _legacy_keywords.unevaluatedProperties_draft2019 

787 ), 

788 "uniqueItems": _keywords.uniqueItems, 

789 }, 

790 type_checker=_types.draft201909_type_checker, 

791 format_checker=_format.draft201909_format_checker, 

792 version="draft2019-09", 

793) 

794 

795Draft202012Validator = create( 

796 meta_schema=SPECIFICATIONS.contents( 

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

798 ), 

799 validators={ 

800 "$dynamicRef": _keywords.dynamicRef, 

801 "$ref": _keywords.ref, 

802 "additionalProperties": _keywords.additionalProperties, 

803 "allOf": _keywords.allOf, 

804 "anyOf": _keywords.anyOf, 

805 "const": _keywords.const, 

806 "contains": _keywords.contains, 

807 "dependentRequired": _keywords.dependentRequired, 

808 "dependentSchemas": _keywords.dependentSchemas, 

809 "enum": _keywords.enum, 

810 "exclusiveMaximum": _keywords.exclusiveMaximum, 

811 "exclusiveMinimum": _keywords.exclusiveMinimum, 

812 "format": _keywords.format, 

813 "if": _keywords.if_, 

814 "items": _keywords.items, 

815 "maxItems": _keywords.maxItems, 

816 "maxLength": _keywords.maxLength, 

817 "maxProperties": _keywords.maxProperties, 

818 "maximum": _keywords.maximum, 

819 "minItems": _keywords.minItems, 

820 "minLength": _keywords.minLength, 

821 "minProperties": _keywords.minProperties, 

822 "minimum": _keywords.minimum, 

823 "multipleOf": _keywords.multipleOf, 

824 "not": _keywords.not_, 

825 "oneOf": _keywords.oneOf, 

826 "pattern": _keywords.pattern, 

827 "patternProperties": _keywords.patternProperties, 

828 "prefixItems": _keywords.prefixItems, 

829 "properties": _keywords.properties, 

830 "propertyNames": _keywords.propertyNames, 

831 "required": _keywords.required, 

832 "type": _keywords.type, 

833 "unevaluatedItems": _keywords.unevaluatedItems, 

834 "unevaluatedProperties": _keywords.unevaluatedProperties, 

835 "uniqueItems": _keywords.uniqueItems, 

836 }, 

837 type_checker=_types.draft202012_type_checker, 

838 format_checker=_format.draft202012_format_checker, 

839 version="draft2020-12", 

840) 

841 

842_LATEST_VERSION = Draft202012Validator 

843 

844 

845class _RefResolver: 

846 """ 

847 Resolve JSON References. 

848 

849 Arguments: 

850 

851 base_uri (str): 

852 

853 The URI of the referring document 

854 

855 referrer: 

856 

857 The actual referring document 

858 

859 store (dict): 

860 

861 A mapping from URIs to documents to cache 

862 

863 cache_remote (bool): 

864 

865 Whether remote refs should be cached after first resolution 

866 

867 handlers (dict): 

868 

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

870 to retrieve them 

871 

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

873 

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

875 the resolution scope to subscopes. 

876 

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

878 

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

880 resolved remote URLs. 

881 

882 Attributes: 

883 

884 cache_remote (bool): 

885 

886 Whether remote refs should be cached after first resolution 

887 

888 .. deprecated:: v4.18.0 

889 

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

891 """ 

892 

893 _DEPRECATION_MESSAGE = ( 

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

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

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

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

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

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

900 ) 

901 

902 def __init__( 

903 self, 

904 base_uri, 

905 referrer, 

906 store=HashTrieMap(), 

907 cache_remote=True, 

908 handlers=(), 

909 urljoin_cache=None, 

910 remote_cache=None, 

911 ): 

912 if urljoin_cache is None: 

913 urljoin_cache = lru_cache(1024)(urljoin) 

914 if remote_cache is None: 

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

916 

917 self.referrer = referrer 

918 self.cache_remote = cache_remote 

919 self.handlers = dict(handlers) 

920 

921 self._scopes_stack = [base_uri] 

922 

923 self.store = _utils.URIDict( 

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

925 ) 

926 self.store.update( 

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

928 ) 

929 self.store.update(store) 

930 self.store.update( 

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

932 for schema in store.values() 

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

934 ) 

935 self.store[base_uri] = referrer 

936 

937 self._urljoin_cache = urljoin_cache 

938 self._remote_cache = remote_cache 

939 

940 @classmethod 

941 def from_schema( # noqa: D417 

942 cls, 

943 schema, 

944 id_of=referencing.jsonschema.DRAFT202012.id_of, 

945 *args, 

946 **kwargs, 

947 ): 

948 """ 

949 Construct a resolver from a JSON schema object. 

950 

951 Arguments: 

952 

953 schema: 

954 

955 the referring schema 

956 

957 Returns: 

958 

959 `_RefResolver` 

960 """ 

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

962 

963 def push_scope(self, scope): 

964 """ 

965 Enter a given sub-scope. 

966 

967 Treats further dereferences as being performed underneath the 

968 given scope. 

969 """ 

970 self._scopes_stack.append( 

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

972 ) 

973 

974 def pop_scope(self): 

975 """ 

976 Exit the most recent entered scope. 

977 

978 Treats further dereferences as being performed underneath the 

979 original scope. 

980 

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

982 called. 

983 """ 

984 try: 

985 self._scopes_stack.pop() 

986 except IndexError: 

987 raise exceptions._RefResolutionError( 

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

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

990 "`push_scope()`", 

991 ) 

992 

993 @property 

994 def resolution_scope(self): 

995 """ 

996 Retrieve the current resolution scope. 

997 """ 

998 return self._scopes_stack[-1] 

999 

1000 @property 

1001 def base_uri(self): 

1002 """ 

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

1004 """ 

1005 uri, _ = urldefrag(self.resolution_scope) 

1006 return uri 

1007 

1008 @contextlib.contextmanager 

1009 def in_scope(self, scope): 

1010 """ 

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

1012 

1013 .. deprecated:: v4.0.0 

1014 """ 

1015 warnings.warn( 

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

1017 "removed in a future release.", 

1018 DeprecationWarning, 

1019 stacklevel=3, 

1020 ) 

1021 self.push_scope(scope) 

1022 try: 

1023 yield 

1024 finally: 

1025 self.pop_scope() 

1026 

1027 @contextlib.contextmanager 

1028 def resolving(self, ref): 

1029 """ 

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

1031 

1032 Exits the scope on exit of this context manager. 

1033 

1034 Arguments: 

1035 

1036 ref (str): 

1037 

1038 The reference to resolve 

1039 """ 

1040 url, resolved = self.resolve(ref) 

1041 self.push_scope(url) 

1042 try: 

1043 yield resolved 

1044 finally: 

1045 self.pop_scope() 

1046 

1047 def _find_in_referrer(self, key): 

1048 return self._get_subschemas_cache()[key] 

1049 

1050 @lru_cache # noqa: B019 

1051 def _get_subschemas_cache(self): 

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

1053 for keyword, subschema in _search_schema( 

1054 self.referrer, _match_subschema_keywords, 

1055 ): 

1056 cache[keyword].append(subschema) 

1057 return cache 

1058 

1059 @lru_cache # noqa: B019 

1060 def _find_in_subschemas(self, url): 

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

1062 if not subschemas: 

1063 return None 

1064 uri, fragment = urldefrag(url) 

1065 for subschema in subschemas: 

1066 id = subschema["$id"] 

1067 if not isinstance(id, str): 

1068 continue 

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

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

1071 if fragment: 

1072 subschema = self.resolve_fragment(subschema, fragment) 

1073 self.store[url] = subschema 

1074 return url, subschema 

1075 return None 

1076 

1077 def resolve(self, ref): 

1078 """ 

1079 Resolve the given reference. 

1080 """ 

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

1082 

1083 match = self._find_in_subschemas(url) 

1084 if match is not None: 

1085 return match 

1086 

1087 return url, self._remote_cache(url) 

1088 

1089 def resolve_from_url(self, url): 

1090 """ 

1091 Resolve the given URL. 

1092 """ 

1093 url, fragment = urldefrag(url) 

1094 if not url: 

1095 url = self.base_uri 

1096 

1097 try: 

1098 document = self.store[url] 

1099 except KeyError: 

1100 try: 

1101 document = self.resolve_remote(url) 

1102 except Exception as exc: 

1103 raise exceptions._RefResolutionError(exc) 

1104 

1105 return self.resolve_fragment(document, fragment) 

1106 

1107 def resolve_fragment(self, document, fragment): 

1108 """ 

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

1110 

1111 Arguments: 

1112 

1113 document: 

1114 

1115 The referent document 

1116 

1117 fragment (str): 

1118 

1119 a URI fragment to resolve within it 

1120 """ 

1121 fragment = fragment.lstrip("/") 

1122 

1123 if not fragment: 

1124 return document 

1125 

1126 if document is self.referrer: 

1127 find = self._find_in_referrer 

1128 else: 

1129 

1130 def find(key): 

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

1132 

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

1134 for subschema in find(keyword): 

1135 if fragment == subschema[keyword]: 

1136 return subschema 

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

1138 for subschema in find(keyword): 

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

1140 return subschema 

1141 

1142 # Resolve via path 

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

1144 for part in parts: 

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

1146 

1147 if isinstance(document, Sequence): 

1148 try: # noqa: SIM105 

1149 part = int(part) 

1150 except ValueError: 

1151 pass 

1152 try: 

1153 document = document[part] 

1154 except (TypeError, LookupError): 

1155 raise exceptions._RefResolutionError( 

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

1157 ) 

1158 

1159 return document 

1160 

1161 def resolve_remote(self, uri): 

1162 """ 

1163 Resolve a remote ``uri``. 

1164 

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

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

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

1168 

1169 .. note:: 

1170 

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

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

1173 detected and used. 

1174 

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

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

1177 

1178 Arguments: 

1179 

1180 uri (str): 

1181 

1182 The URI to resolve 

1183 

1184 Returns: 

1185 

1186 The retrieved document 

1187 

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

1189 """ 

1190 try: 

1191 import requests 

1192 except ImportError: 

1193 requests = None 

1194 

1195 scheme = urlsplit(uri).scheme 

1196 

1197 if scheme in self.handlers: 

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

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

1200 # Requests has support for detecting the correct encoding of 

1201 # json over http 

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

1203 else: 

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

1205 with urlopen(uri) as url: 

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

1207 

1208 if self.cache_remote: 

1209 self.store[uri] = result 

1210 return result 

1211 

1212 

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

1214 

1215 

1216def _match_keyword(keyword): 

1217 

1218 def matcher(value): 

1219 if keyword in value: 

1220 yield value 

1221 

1222 return matcher 

1223 

1224 

1225def _match_subschema_keywords(value): 

1226 for keyword in _SUBSCHEMAS_KEYWORDS: 

1227 if keyword in value: 

1228 yield keyword, value 

1229 

1230 

1231def _search_schema(schema, matcher): 

1232 """Breadth-first search routine.""" 

1233 values = deque([schema]) 

1234 while values: 

1235 value = values.pop() 

1236 if not isinstance(value, dict): 

1237 continue 

1238 yield from matcher(value) 

1239 values.extendleft(value.values()) 

1240 

1241 

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

1243 """ 

1244 Validate an instance under the given schema. 

1245 

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

1247 Traceback (most recent call last): 

1248 ... 

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

1250 

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

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

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

1254 

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

1256 if you intend to validate multiple instances with 

1257 the same schema, you likely would prefer using the 

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

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

1260 

1261 

1262 Arguments: 

1263 

1264 instance: 

1265 

1266 The instance to validate 

1267 

1268 schema: 

1269 

1270 The schema to validate with 

1271 

1272 cls (jsonschema.protocols.Validator): 

1273 

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

1275 

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

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

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

1279 proper validator will be used. The specification recommends that 

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

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

1282 latest released draft. 

1283 

1284 Any other provided positional and keyword arguments will be passed 

1285 on when instantiating the ``cls``. 

1286 

1287 Raises: 

1288 

1289 `jsonschema.exceptions.ValidationError`: 

1290 

1291 if the instance is invalid 

1292 

1293 `jsonschema.exceptions.SchemaError`: 

1294 

1295 if the schema itself is invalid 

1296 

1297 .. rubric:: Footnotes 

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

1299 `jsonschema.validators.validates` 

1300 """ 

1301 if cls is None: 

1302 cls = validator_for(schema) 

1303 

1304 cls.check_schema(schema) 

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

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

1307 if error is not None: 

1308 raise error 

1309 

1310 

1311def validator_for(schema, default=_UNSET): 

1312 """ 

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

1314 

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

1316 schema to look up the appropriate validator class. 

1317 

1318 Arguments: 

1319 

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

1321 

1322 the schema to look at 

1323 

1324 default: 

1325 

1326 the default to return if the appropriate validator class 

1327 cannot be determined. 

1328 

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

1330 draft. 

1331 

1332 Examples: 

1333 

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

1335 class is returned: 

1336 

1337 >>> schema = { 

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

1339 ... "type": "integer", 

1340 ... } 

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

1342 <class 'jsonschema.validators.Draft202012Validator'> 

1343 

1344 

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

1346 

1347 >>> schema = { 

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

1349 ... "type": "integer", 

1350 ... } 

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

1352 <class 'jsonschema.validators.Draft7Validator'> 

1353 

1354 

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

1356 argument: 

1357 

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

1359 >>> jsonschema.validators.validator_for( 

1360 ... schema, default=Draft7Validator, 

1361 ... ) 

1362 <class 'jsonschema.validators.Draft7Validator'> 

1363 

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

1365 Always including the keyword when authoring schemas is highly 

1366 recommended. 

1367 

1368 """ 

1369 DefaultValidator = _LATEST_VERSION if default is _UNSET else default 

1370 

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

1372 return DefaultValidator 

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

1374 warn( 

1375 ( 

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

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

1378 "an error in the future." 

1379 ), 

1380 DeprecationWarning, 

1381 stacklevel=2, 

1382 ) 

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