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

383 statements  

« prev     ^ index     » next       coverage.py v7.4.0, created at 2024-01-22 06:29 +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 typing import TYPE_CHECKING 

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

12from urllib.request import urlopen 

13from warnings import warn 

14import contextlib 

15import json 

16import reprlib 

17import warnings 

18 

19from attrs import define, field, fields 

20from jsonschema_specifications import REGISTRY as SPECIFICATIONS 

21from rpds import HashTrieMap 

22import referencing.exceptions 

23import referencing.jsonschema 

24 

25from jsonschema import ( 

26 _format, 

27 _keywords, 

28 _legacy_keywords, 

29 _types, 

30 _typing, 

31 _utils, 

32 exceptions, 

33) 

34 

35if TYPE_CHECKING: 

36 from jsonschema.protocols import Validator 

37 

38_UNSET = _utils.Unset() 

39 

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

41_META_SCHEMAS = _utils.URIDict() 

42 

43 

44def __getattr__(name): 

45 if name == "ErrorTree": 

46 warnings.warn( 

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

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

49 DeprecationWarning, 

50 stacklevel=2, 

51 ) 

52 from jsonschema.exceptions import ErrorTree 

53 return ErrorTree 

54 elif name == "validators": 

55 warnings.warn( 

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

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

58 DeprecationWarning, 

59 stacklevel=2, 

60 ) 

61 return _VALIDATORS 

62 elif name == "meta_schemas": 

63 warnings.warn( 

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

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

66 DeprecationWarning, 

67 stacklevel=2, 

68 ) 

69 return _META_SCHEMAS 

70 elif name == "RefResolver": 

71 warnings.warn( 

72 _RefResolver._DEPRECATION_MESSAGE, 

73 DeprecationWarning, 

74 stacklevel=2, 

75 ) 

76 return _RefResolver 

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

78 

79 

80def validates(version): 

81 """ 

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

83 

84 Registered validators and their meta schemas will be considered when 

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

86 

87 Arguments: 

88 

89 version (str): 

90 

91 An identifier to use as the version's name 

92 

93 Returns: 

94 

95 collections.abc.Callable: 

96 

97 a class decorator to decorate the validator with the version 

98 """ 

99 

100 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): 

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 # preemptively don't shadow the `Validator.format_checker` local 

214 format_checker_arg = format_checker 

215 

216 specification = referencing.jsonschema.specification_with( 

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

218 default=referencing.Specification.OPAQUE, 

219 ) 

220 

221 @define 

222 class Validator: 

223 

224 VALIDATORS = dict(validators) # noqa: RUF012 

225 META_SCHEMA = dict(meta_schema) # noqa: RUF012 

226 TYPE_CHECKER = type_checker 

227 FORMAT_CHECKER = format_checker_arg 

228 ID_OF = staticmethod(id_of) 

229 

230 _APPLICABLE_VALIDATORS = applicable_validators 

231 

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

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

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

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

236 _registry: referencing.jsonschema.SchemaRegistry = field( 

237 default=_REMOTE_WARNING_REGISTRY, 

238 kw_only=True, 

239 repr=False, 

240 ) 

241 _resolver = field( 

242 alias="_resolver", 

243 default=None, 

244 kw_only=True, 

245 repr=False, 

246 ) 

247 

248 def __init_subclass__(cls): 

249 warnings.warn( 

250 ( 

251 "Subclassing validator classes is not intended to " 

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

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

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

255 "between releases of jsonschema. Instead, prefer " 

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

257 "owned entirely by the downstream library." 

258 ), 

259 DeprecationWarning, 

260 stacklevel=2, 

261 ) 

262 

263 def evolve(self, **changes): 

264 cls = self.__class__ 

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

266 NewValidator = validator_for(schema, default=cls) 

267 

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

269 if not field.init: 

270 continue 

271 attr_name = field.name 

272 init_name = field.alias 

273 if init_name not in changes: 

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

275 

276 return NewValidator(**changes) 

277 

278 cls.evolve = evolve 

279 

280 def __attrs_post_init__(self): 

281 if self._resolver is None: 

282 registry = self._registry 

283 if registry is not _REMOTE_WARNING_REGISTRY: 

284 registry = SPECIFICATIONS.combine(registry) 

285 resource = specification.create_resource(self.schema) 

286 self._resolver = registry.resolver_with_root(resource) 

287 

288 # REMOVEME: Legacy ref resolution state management. 

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

290 if push_scope is not None: 

291 id = id_of(self.schema) 

292 if id is not None: 

293 push_scope(id) 

294 

295 @classmethod 

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

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

298 if format_checker is _UNSET: 

299 format_checker = Validator.FORMAT_CHECKER 

300 validator = Validator( 

301 schema=cls.META_SCHEMA, 

302 format_checker=format_checker, 

303 ) 

304 for error in validator.iter_errors(schema): 

305 raise exceptions.SchemaError.create_from(error) 

306 

307 @property 

308 def resolver(self): 

309 warnings.warn( 

310 ( 

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

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

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

314 "library, which provides more compliant referencing " 

315 "behavior as well as more flexible APIs for " 

316 "customization." 

317 ), 

318 DeprecationWarning, 

319 stacklevel=2, 

320 ) 

321 if self._ref_resolver is None: 

322 self._ref_resolver = _RefResolver.from_schema( 

323 self.schema, 

324 id_of=id_of, 

325 ) 

326 return self._ref_resolver 

327 

328 def evolve(self, **changes): 

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

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

331 

332 for (attr_name, init_name) in evolve_fields: 

333 if init_name not in changes: 

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

335 

336 return NewValidator(**changes) 

337 

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

339 if _schema is not None: 

340 warnings.warn( 

341 ( 

342 "Passing a schema to Validator.iter_errors " 

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

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

345 "iter_errors(...) instead." 

346 ), 

347 DeprecationWarning, 

348 stacklevel=2, 

349 ) 

350 else: 

351 _schema = self.schema 

352 

353 if _schema is True: 

354 return 

355 elif _schema is False: 

356 yield exceptions.ValidationError( 

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

358 validator=None, 

359 validator_value=None, 

360 instance=instance, 

361 schema=_schema, 

362 ) 

363 return 

364 

365 for k, v in applicable_validators(_schema): 

366 validator = self.VALIDATORS.get(k) 

367 if validator is None: 

368 continue 

369 

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

371 for error in errors: 

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

373 error._set( 

374 validator=k, 

375 validator_value=v, 

376 instance=instance, 

377 schema=_schema, 

378 type_checker=self.TYPE_CHECKER, 

379 ) 

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

381 error.schema_path.appendleft(k) 

382 yield error 

383 

384 def descend( 

385 self, 

386 instance, 

387 schema, 

388 path=None, 

389 schema_path=None, 

390 resolver=None, 

391 ): 

392 if schema is True: 

393 return 

394 elif schema is False: 

395 yield exceptions.ValidationError( 

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

397 validator=None, 

398 validator_value=None, 

399 instance=instance, 

400 schema=schema, 

401 ) 

402 return 

403 

404 if self._ref_resolver is not None: 

405 evolved = self.evolve(schema=schema) 

406 else: 

407 if resolver is None: 

408 resolver = self._resolver.in_subresource( 

409 specification.create_resource(schema), 

410 ) 

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

412 

413 for k, v in applicable_validators(schema): 

414 validator = evolved.VALIDATORS.get(k) 

415 if validator is None: 

416 continue 

417 

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

419 for error in errors: 

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

421 error._set( 

422 validator=k, 

423 validator_value=v, 

424 instance=instance, 

425 schema=schema, 

426 type_checker=evolved.TYPE_CHECKER, 

427 ) 

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

429 error.schema_path.appendleft(k) 

430 if path is not None: 

431 error.path.appendleft(path) 

432 if schema_path is not None: 

433 error.schema_path.appendleft(schema_path) 

434 yield error 

435 

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

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

438 raise error 

439 

440 def is_type(self, instance, type): 

441 try: 

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

443 except exceptions.UndefinedTypeCheck: 

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

445 raise exc from None 

446 

447 def _validate_reference(self, ref, instance): 

448 if self._ref_resolver is None: 

449 try: 

450 resolved = self._resolver.lookup(ref) 

451 except referencing.exceptions.Unresolvable as err: 

452 raise exceptions._WrappedReferencingError(err) from err 

453 

454 return self.descend( 

455 instance, 

456 resolved.contents, 

457 resolver=resolved.resolver, 

458 ) 

459 else: 

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

461 if resolve is None: 

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

463 return self.descend(instance, resolved) 

464 else: 

465 scope, resolved = resolve(ref) 

466 self._ref_resolver.push_scope(scope) 

467 

468 try: 

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

470 finally: 

471 self._ref_resolver.pop_scope() 

472 

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

474 if _schema is not None: 

475 warnings.warn( 

476 ( 

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

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

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

480 "instead." 

481 ), 

482 DeprecationWarning, 

483 stacklevel=2, 

484 ) 

485 self = self.evolve(schema=_schema) 

486 

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

488 return error is None 

489 

490 evolve_fields = [ 

491 (field.name, field.alias) 

492 for field in fields(Validator) 

493 if field.init 

494 ] 

495 

496 if version is not None: 

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

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

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

500 

501 return Validator 

502 

503 

504def extend( 

505 validator, 

506 validators=(), 

507 version=None, 

508 type_checker=None, 

509 format_checker=None, 

510): 

511 """ 

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

513 

514 Arguments: 

515 

516 validator (jsonschema.protocols.Validator): 

517 

518 an existing validator class 

519 

520 validators (collections.abc.Mapping): 

521 

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

523 structure is as in `create`. 

524 

525 .. note:: 

526 

527 Any validator callables with the same name as an 

528 existing one will (silently) replace the old validator 

529 callable entirely, effectively overriding any validation 

530 done in the "parent" validator class. 

531 

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

533 validator callable, delegate and call it directly in 

534 the new validator function by retrieving it using 

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

536 

537 version (str): 

538 

539 a version for the new validator class 

540 

541 type_checker (jsonschema.TypeChecker): 

542 

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

544 

545 If unprovided, the type checker of the extended 

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

547 

548 format_checker (jsonschema.FormatChecker): 

549 

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

551 

552 If unprovided, the format checker of the extended 

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

554 

555 Returns: 

556 

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

558 provided 

559 

560 .. note:: Meta Schemas 

561 

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

563 

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

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

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

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

568 old validator. 

569 """ 

570 all_validators = dict(validator.VALIDATORS) 

571 all_validators.update(validators) 

572 

573 if type_checker is None: 

574 type_checker = validator.TYPE_CHECKER 

575 if format_checker is None: 

576 format_checker = validator.FORMAT_CHECKER 

577 return create( 

578 meta_schema=validator.META_SCHEMA, 

579 validators=all_validators, 

580 version=version, 

581 type_checker=type_checker, 

582 format_checker=format_checker, 

583 id_of=validator.ID_OF, 

584 applicable_validators=validator._APPLICABLE_VALIDATORS, 

585 ) 

586 

587 

588Draft3Validator = create( 

589 meta_schema=SPECIFICATIONS.contents( 

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

591 ), 

592 validators={ 

593 "$ref": _keywords.ref, 

594 "additionalItems": _legacy_keywords.additionalItems, 

595 "additionalProperties": _keywords.additionalProperties, 

596 "dependencies": _legacy_keywords.dependencies_draft3, 

597 "disallow": _legacy_keywords.disallow_draft3, 

598 "divisibleBy": _keywords.multipleOf, 

599 "enum": _keywords.enum, 

600 "extends": _legacy_keywords.extends_draft3, 

601 "format": _keywords.format, 

602 "items": _legacy_keywords.items_draft3_draft4, 

603 "maxItems": _keywords.maxItems, 

604 "maxLength": _keywords.maxLength, 

605 "maximum": _legacy_keywords.maximum_draft3_draft4, 

606 "minItems": _keywords.minItems, 

607 "minLength": _keywords.minLength, 

608 "minimum": _legacy_keywords.minimum_draft3_draft4, 

609 "pattern": _keywords.pattern, 

610 "patternProperties": _keywords.patternProperties, 

611 "properties": _legacy_keywords.properties_draft3, 

612 "type": _legacy_keywords.type_draft3, 

613 "uniqueItems": _keywords.uniqueItems, 

614 }, 

615 type_checker=_types.draft3_type_checker, 

616 format_checker=_format.draft3_format_checker, 

617 version="draft3", 

618 id_of=referencing.jsonschema.DRAFT3.id_of, 

619 applicable_validators=_legacy_keywords.ignore_ref_siblings, 

620) 

621 

622Draft4Validator = create( 

623 meta_schema=SPECIFICATIONS.contents( 

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

625 ), 

626 validators={ 

627 "$ref": _keywords.ref, 

628 "additionalItems": _legacy_keywords.additionalItems, 

629 "additionalProperties": _keywords.additionalProperties, 

630 "allOf": _keywords.allOf, 

631 "anyOf": _keywords.anyOf, 

632 "dependencies": _legacy_keywords.dependencies_draft4_draft6_draft7, 

633 "enum": _keywords.enum, 

634 "format": _keywords.format, 

635 "items": _legacy_keywords.items_draft3_draft4, 

636 "maxItems": _keywords.maxItems, 

637 "maxLength": _keywords.maxLength, 

638 "maxProperties": _keywords.maxProperties, 

639 "maximum": _legacy_keywords.maximum_draft3_draft4, 

640 "minItems": _keywords.minItems, 

641 "minLength": _keywords.minLength, 

642 "minProperties": _keywords.minProperties, 

643 "minimum": _legacy_keywords.minimum_draft3_draft4, 

644 "multipleOf": _keywords.multipleOf, 

645 "not": _keywords.not_, 

646 "oneOf": _keywords.oneOf, 

647 "pattern": _keywords.pattern, 

648 "patternProperties": _keywords.patternProperties, 

649 "properties": _keywords.properties, 

650 "required": _keywords.required, 

651 "type": _keywords.type, 

652 "uniqueItems": _keywords.uniqueItems, 

653 }, 

654 type_checker=_types.draft4_type_checker, 

655 format_checker=_format.draft4_format_checker, 

656 version="draft4", 

657 id_of=referencing.jsonschema.DRAFT4.id_of, 

658 applicable_validators=_legacy_keywords.ignore_ref_siblings, 

659) 

660 

661Draft6Validator = create( 

662 meta_schema=SPECIFICATIONS.contents( 

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

664 ), 

665 validators={ 

666 "$ref": _keywords.ref, 

667 "additionalItems": _legacy_keywords.additionalItems, 

668 "additionalProperties": _keywords.additionalProperties, 

669 "allOf": _keywords.allOf, 

670 "anyOf": _keywords.anyOf, 

671 "const": _keywords.const, 

672 "contains": _legacy_keywords.contains_draft6_draft7, 

673 "dependencies": _legacy_keywords.dependencies_draft4_draft6_draft7, 

674 "enum": _keywords.enum, 

675 "exclusiveMaximum": _keywords.exclusiveMaximum, 

676 "exclusiveMinimum": _keywords.exclusiveMinimum, 

677 "format": _keywords.format, 

678 "items": _legacy_keywords.items_draft6_draft7_draft201909, 

679 "maxItems": _keywords.maxItems, 

680 "maxLength": _keywords.maxLength, 

681 "maxProperties": _keywords.maxProperties, 

682 "maximum": _keywords.maximum, 

683 "minItems": _keywords.minItems, 

684 "minLength": _keywords.minLength, 

685 "minProperties": _keywords.minProperties, 

686 "minimum": _keywords.minimum, 

687 "multipleOf": _keywords.multipleOf, 

688 "not": _keywords.not_, 

689 "oneOf": _keywords.oneOf, 

690 "pattern": _keywords.pattern, 

691 "patternProperties": _keywords.patternProperties, 

692 "properties": _keywords.properties, 

693 "propertyNames": _keywords.propertyNames, 

694 "required": _keywords.required, 

695 "type": _keywords.type, 

696 "uniqueItems": _keywords.uniqueItems, 

697 }, 

698 type_checker=_types.draft6_type_checker, 

699 format_checker=_format.draft6_format_checker, 

700 version="draft6", 

701 id_of=referencing.jsonschema.DRAFT6.id_of, 

702 applicable_validators=_legacy_keywords.ignore_ref_siblings, 

703) 

704 

705Draft7Validator = create( 

706 meta_schema=SPECIFICATIONS.contents( 

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

708 ), 

709 validators={ 

710 "$ref": _keywords.ref, 

711 "additionalItems": _legacy_keywords.additionalItems, 

712 "additionalProperties": _keywords.additionalProperties, 

713 "allOf": _keywords.allOf, 

714 "anyOf": _keywords.anyOf, 

715 "const": _keywords.const, 

716 "contains": _legacy_keywords.contains_draft6_draft7, 

717 "dependencies": _legacy_keywords.dependencies_draft4_draft6_draft7, 

718 "enum": _keywords.enum, 

719 "exclusiveMaximum": _keywords.exclusiveMaximum, 

720 "exclusiveMinimum": _keywords.exclusiveMinimum, 

721 "format": _keywords.format, 

722 "if": _keywords.if_, 

723 "items": _legacy_keywords.items_draft6_draft7_draft201909, 

724 "maxItems": _keywords.maxItems, 

725 "maxLength": _keywords.maxLength, 

726 "maxProperties": _keywords.maxProperties, 

727 "maximum": _keywords.maximum, 

728 "minItems": _keywords.minItems, 

729 "minLength": _keywords.minLength, 

730 "minProperties": _keywords.minProperties, 

731 "minimum": _keywords.minimum, 

732 "multipleOf": _keywords.multipleOf, 

733 "not": _keywords.not_, 

734 "oneOf": _keywords.oneOf, 

735 "pattern": _keywords.pattern, 

736 "patternProperties": _keywords.patternProperties, 

737 "properties": _keywords.properties, 

738 "propertyNames": _keywords.propertyNames, 

739 "required": _keywords.required, 

740 "type": _keywords.type, 

741 "uniqueItems": _keywords.uniqueItems, 

742 }, 

743 type_checker=_types.draft7_type_checker, 

744 format_checker=_format.draft7_format_checker, 

745 version="draft7", 

746 id_of=referencing.jsonschema.DRAFT7.id_of, 

747 applicable_validators=_legacy_keywords.ignore_ref_siblings, 

748) 

749 

750Draft201909Validator = create( 

751 meta_schema=SPECIFICATIONS.contents( 

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

753 ), 

754 validators={ 

755 "$recursiveRef": _legacy_keywords.recursiveRef, 

756 "$ref": _keywords.ref, 

757 "additionalItems": _legacy_keywords.additionalItems, 

758 "additionalProperties": _keywords.additionalProperties, 

759 "allOf": _keywords.allOf, 

760 "anyOf": _keywords.anyOf, 

761 "const": _keywords.const, 

762 "contains": _keywords.contains, 

763 "dependentRequired": _keywords.dependentRequired, 

764 "dependentSchemas": _keywords.dependentSchemas, 

765 "enum": _keywords.enum, 

766 "exclusiveMaximum": _keywords.exclusiveMaximum, 

767 "exclusiveMinimum": _keywords.exclusiveMinimum, 

768 "format": _keywords.format, 

769 "if": _keywords.if_, 

770 "items": _legacy_keywords.items_draft6_draft7_draft201909, 

771 "maxItems": _keywords.maxItems, 

772 "maxLength": _keywords.maxLength, 

773 "maxProperties": _keywords.maxProperties, 

774 "maximum": _keywords.maximum, 

775 "minItems": _keywords.minItems, 

776 "minLength": _keywords.minLength, 

777 "minProperties": _keywords.minProperties, 

778 "minimum": _keywords.minimum, 

779 "multipleOf": _keywords.multipleOf, 

780 "not": _keywords.not_, 

781 "oneOf": _keywords.oneOf, 

782 "pattern": _keywords.pattern, 

783 "patternProperties": _keywords.patternProperties, 

784 "properties": _keywords.properties, 

785 "propertyNames": _keywords.propertyNames, 

786 "required": _keywords.required, 

787 "type": _keywords.type, 

788 "unevaluatedItems": _legacy_keywords.unevaluatedItems_draft2019, 

789 "unevaluatedProperties": ( 

790 _legacy_keywords.unevaluatedProperties_draft2019 

791 ), 

792 "uniqueItems": _keywords.uniqueItems, 

793 }, 

794 type_checker=_types.draft201909_type_checker, 

795 format_checker=_format.draft201909_format_checker, 

796 version="draft2019-09", 

797) 

798 

799Draft202012Validator = create( 

800 meta_schema=SPECIFICATIONS.contents( 

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

802 ), 

803 validators={ 

804 "$dynamicRef": _keywords.dynamicRef, 

805 "$ref": _keywords.ref, 

806 "additionalProperties": _keywords.additionalProperties, 

807 "allOf": _keywords.allOf, 

808 "anyOf": _keywords.anyOf, 

809 "const": _keywords.const, 

810 "contains": _keywords.contains, 

811 "dependentRequired": _keywords.dependentRequired, 

812 "dependentSchemas": _keywords.dependentSchemas, 

813 "enum": _keywords.enum, 

814 "exclusiveMaximum": _keywords.exclusiveMaximum, 

815 "exclusiveMinimum": _keywords.exclusiveMinimum, 

816 "format": _keywords.format, 

817 "if": _keywords.if_, 

818 "items": _keywords.items, 

819 "maxItems": _keywords.maxItems, 

820 "maxLength": _keywords.maxLength, 

821 "maxProperties": _keywords.maxProperties, 

822 "maximum": _keywords.maximum, 

823 "minItems": _keywords.minItems, 

824 "minLength": _keywords.minLength, 

825 "minProperties": _keywords.minProperties, 

826 "minimum": _keywords.minimum, 

827 "multipleOf": _keywords.multipleOf, 

828 "not": _keywords.not_, 

829 "oneOf": _keywords.oneOf, 

830 "pattern": _keywords.pattern, 

831 "patternProperties": _keywords.patternProperties, 

832 "prefixItems": _keywords.prefixItems, 

833 "properties": _keywords.properties, 

834 "propertyNames": _keywords.propertyNames, 

835 "required": _keywords.required, 

836 "type": _keywords.type, 

837 "unevaluatedItems": _keywords.unevaluatedItems, 

838 "unevaluatedProperties": _keywords.unevaluatedProperties, 

839 "uniqueItems": _keywords.uniqueItems, 

840 }, 

841 type_checker=_types.draft202012_type_checker, 

842 format_checker=_format.draft202012_format_checker, 

843 version="draft2020-12", 

844) 

845 

846_LATEST_VERSION = Draft202012Validator 

847 

848 

849class _RefResolver: 

850 """ 

851 Resolve JSON References. 

852 

853 Arguments: 

854 

855 base_uri (str): 

856 

857 The URI of the referring document 

858 

859 referrer: 

860 

861 The actual referring document 

862 

863 store (dict): 

864 

865 A mapping from URIs to documents to cache 

866 

867 cache_remote (bool): 

868 

869 Whether remote refs should be cached after first resolution 

870 

871 handlers (dict): 

872 

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

874 to retrieve them 

875 

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

877 

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

879 the resolution scope to subscopes. 

880 

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

882 

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

884 resolved remote URLs. 

885 

886 Attributes: 

887 

888 cache_remote (bool): 

889 

890 Whether remote refs should be cached after first resolution 

891 

892 .. deprecated:: v4.18.0 

893 

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

895 """ 

896 

897 _DEPRECATION_MESSAGE = ( 

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

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

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

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

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

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

904 ) 

905 

906 def __init__( 

907 self, 

908 base_uri, 

909 referrer, 

910 store=HashTrieMap(), 

911 cache_remote=True, 

912 handlers=(), 

913 urljoin_cache=None, 

914 remote_cache=None, 

915 ): 

916 if urljoin_cache is None: 

917 urljoin_cache = lru_cache(1024)(urljoin) 

918 if remote_cache is None: 

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

920 

921 self.referrer = referrer 

922 self.cache_remote = cache_remote 

923 self.handlers = dict(handlers) 

924 

925 self._scopes_stack = [base_uri] 

926 

927 self.store = _utils.URIDict( 

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

929 ) 

930 self.store.update( 

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

932 ) 

933 self.store.update(store) 

934 self.store.update( 

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

936 for schema in store.values() 

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

938 ) 

939 self.store[base_uri] = referrer 

940 

941 self._urljoin_cache = urljoin_cache 

942 self._remote_cache = remote_cache 

943 

944 @classmethod 

945 def from_schema( # noqa: D417 

946 cls, 

947 schema, 

948 id_of=referencing.jsonschema.DRAFT202012.id_of, 

949 *args, 

950 **kwargs, 

951 ): 

952 """ 

953 Construct a resolver from a JSON schema object. 

954 

955 Arguments: 

956 

957 schema: 

958 

959 the referring schema 

960 

961 Returns: 

962 

963 `_RefResolver` 

964 """ 

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

966 

967 def push_scope(self, scope): 

968 """ 

969 Enter a given sub-scope. 

970 

971 Treats further dereferences as being performed underneath the 

972 given scope. 

973 """ 

974 self._scopes_stack.append( 

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

976 ) 

977 

978 def pop_scope(self): 

979 """ 

980 Exit the most recent entered scope. 

981 

982 Treats further dereferences as being performed underneath the 

983 original scope. 

984 

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

986 called. 

987 """ 

988 try: 

989 self._scopes_stack.pop() 

990 except IndexError: 

991 raise exceptions._RefResolutionError( 

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

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

994 "`push_scope()`", 

995 ) from None 

996 

997 @property 

998 def resolution_scope(self): 

999 """ 

1000 Retrieve the current resolution scope. 

1001 """ 

1002 return self._scopes_stack[-1] 

1003 

1004 @property 

1005 def base_uri(self): 

1006 """ 

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

1008 """ 

1009 uri, _ = urldefrag(self.resolution_scope) 

1010 return uri 

1011 

1012 @contextlib.contextmanager 

1013 def in_scope(self, scope): 

1014 """ 

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

1016 

1017 .. deprecated:: v4.0.0 

1018 """ 

1019 warnings.warn( 

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

1021 "removed in a future release.", 

1022 DeprecationWarning, 

1023 stacklevel=3, 

1024 ) 

1025 self.push_scope(scope) 

1026 try: 

1027 yield 

1028 finally: 

1029 self.pop_scope() 

1030 

1031 @contextlib.contextmanager 

1032 def resolving(self, ref): 

1033 """ 

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

1035 

1036 Exits the scope on exit of this context manager. 

1037 

1038 Arguments: 

1039 

1040 ref (str): 

1041 

1042 The reference to resolve 

1043 """ 

1044 url, resolved = self.resolve(ref) 

1045 self.push_scope(url) 

1046 try: 

1047 yield resolved 

1048 finally: 

1049 self.pop_scope() 

1050 

1051 def _find_in_referrer(self, key): 

1052 return self._get_subschemas_cache()[key] 

1053 

1054 @lru_cache # noqa: B019 

1055 def _get_subschemas_cache(self): 

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

1057 for keyword, subschema in _search_schema( 

1058 self.referrer, _match_subschema_keywords, 

1059 ): 

1060 cache[keyword].append(subschema) 

1061 return cache 

1062 

1063 @lru_cache # noqa: B019 

1064 def _find_in_subschemas(self, url): 

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

1066 if not subschemas: 

1067 return None 

1068 uri, fragment = urldefrag(url) 

1069 for subschema in subschemas: 

1070 id = subschema["$id"] 

1071 if not isinstance(id, str): 

1072 continue 

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

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

1075 if fragment: 

1076 subschema = self.resolve_fragment(subschema, fragment) 

1077 self.store[url] = subschema 

1078 return url, subschema 

1079 return None 

1080 

1081 def resolve(self, ref): 

1082 """ 

1083 Resolve the given reference. 

1084 """ 

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

1086 

1087 match = self._find_in_subschemas(url) 

1088 if match is not None: 

1089 return match 

1090 

1091 return url, self._remote_cache(url) 

1092 

1093 def resolve_from_url(self, url): 

1094 """ 

1095 Resolve the given URL. 

1096 """ 

1097 url, fragment = urldefrag(url) 

1098 if not url: 

1099 url = self.base_uri 

1100 

1101 try: 

1102 document = self.store[url] 

1103 except KeyError: 

1104 try: 

1105 document = self.resolve_remote(url) 

1106 except Exception as exc: # noqa: BLE001 

1107 raise exceptions._RefResolutionError(exc) from exc 

1108 

1109 return self.resolve_fragment(document, fragment) 

1110 

1111 def resolve_fragment(self, document, fragment): 

1112 """ 

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

1114 

1115 Arguments: 

1116 

1117 document: 

1118 

1119 The referent document 

1120 

1121 fragment (str): 

1122 

1123 a URI fragment to resolve within it 

1124 """ 

1125 fragment = fragment.lstrip("/") 

1126 

1127 if not fragment: 

1128 return document 

1129 

1130 if document is self.referrer: 

1131 find = self._find_in_referrer 

1132 else: 

1133 

1134 def find(key): 

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

1136 

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

1138 for subschema in find(keyword): 

1139 if fragment == subschema[keyword]: 

1140 return subschema 

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

1142 for subschema in find(keyword): 

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

1144 return subschema 

1145 

1146 # Resolve via path 

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

1148 for part in parts: 

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

1150 

1151 if isinstance(document, Sequence): 

1152 try: # noqa: SIM105 

1153 part = int(part) 

1154 except ValueError: 

1155 pass 

1156 try: 

1157 document = document[part] 

1158 except (TypeError, LookupError) as err: 

1159 raise exceptions._RefResolutionError( 

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

1161 ) from err 

1162 

1163 return document 

1164 

1165 def resolve_remote(self, uri): 

1166 """ 

1167 Resolve a remote ``uri``. 

1168 

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

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

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

1172 

1173 .. note:: 

1174 

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

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

1177 detected and used. 

1178 

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

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

1181 

1182 Arguments: 

1183 

1184 uri (str): 

1185 

1186 The URI to resolve 

1187 

1188 Returns: 

1189 

1190 The retrieved document 

1191 

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

1193 """ 

1194 try: 

1195 import requests 

1196 except ImportError: 

1197 requests = None 

1198 

1199 scheme = urlsplit(uri).scheme 

1200 

1201 if scheme in self.handlers: 

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

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

1204 # Requests has support for detecting the correct encoding of 

1205 # json over http 

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

1207 else: 

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

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

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

1211 

1212 if self.cache_remote: 

1213 self.store[uri] = result 

1214 return result 

1215 

1216 

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

1218 

1219 

1220def _match_keyword(keyword): 

1221 

1222 def matcher(value): 

1223 if keyword in value: 

1224 yield value 

1225 

1226 return matcher 

1227 

1228 

1229def _match_subschema_keywords(value): 

1230 for keyword in _SUBSCHEMAS_KEYWORDS: 

1231 if keyword in value: 

1232 yield keyword, value 

1233 

1234 

1235def _search_schema(schema, matcher): 

1236 """Breadth-first search routine.""" 

1237 values = deque([schema]) 

1238 while values: 

1239 value = values.pop() 

1240 if not isinstance(value, dict): 

1241 continue 

1242 yield from matcher(value) 

1243 values.extendleft(value.values()) 

1244 

1245 

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

1247 """ 

1248 Validate an instance under the given schema. 

1249 

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

1251 Traceback (most recent call last): 

1252 ... 

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

1254 

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

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

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

1258 

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

1260 if you intend to validate multiple instances with 

1261 the same schema, you likely would prefer using the 

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

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

1264 

1265 

1266 Arguments: 

1267 

1268 instance: 

1269 

1270 The instance to validate 

1271 

1272 schema: 

1273 

1274 The schema to validate with 

1275 

1276 cls (jsonschema.protocols.Validator): 

1277 

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

1279 

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

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

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

1283 proper validator will be used. The specification recommends that 

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

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

1286 latest released draft. 

1287 

1288 Any other provided positional and keyword arguments will be passed 

1289 on when instantiating the ``cls``. 

1290 

1291 Raises: 

1292 

1293 `jsonschema.exceptions.ValidationError`: 

1294 

1295 if the instance is invalid 

1296 

1297 `jsonschema.exceptions.SchemaError`: 

1298 

1299 if the schema itself is invalid 

1300 

1301 .. rubric:: Footnotes 

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

1303 `jsonschema.validators.validates` 

1304 """ 

1305 if cls is None: 

1306 cls = validator_for(schema) 

1307 

1308 cls.check_schema(schema) 

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

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

1311 if error is not None: 

1312 raise error 

1313 

1314 

1315def validator_for(schema, default=_UNSET) -> Validator: 

1316 """ 

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

1318 

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

1320 schema to look up the appropriate validator class. 

1321 

1322 Arguments: 

1323 

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

1325 

1326 the schema to look at 

1327 

1328 default: 

1329 

1330 the default to return if the appropriate validator class 

1331 cannot be determined. 

1332 

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

1334 draft. 

1335 

1336 Examples: 

1337 

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

1339 class is returned: 

1340 

1341 >>> schema = { 

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

1343 ... "type": "integer", 

1344 ... } 

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

1346 <class 'jsonschema.validators.Draft202012Validator'> 

1347 

1348 

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

1350 

1351 >>> schema = { 

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

1353 ... "type": "integer", 

1354 ... } 

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

1356 <class 'jsonschema.validators.Draft7Validator'> 

1357 

1358 

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

1360 argument: 

1361 

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

1363 >>> jsonschema.validators.validator_for( 

1364 ... schema, default=Draft7Validator, 

1365 ... ) 

1366 <class 'jsonschema.validators.Draft7Validator'> 

1367 

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

1369 Always including the keyword when authoring schemas is highly 

1370 recommended. 

1371 

1372 """ 

1373 DefaultValidator = _LATEST_VERSION if default is _UNSET else default 

1374 

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

1376 return DefaultValidator 

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

1378 warn( 

1379 ( 

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

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

1382 "an error in the future." 

1383 ), 

1384 DeprecationWarning, 

1385 stacklevel=2, 

1386 ) 

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