Spaces:
Paused
Paused
| """ | |
| The ``jsonschema`` command line. | |
| """ | |
| from importlib import metadata | |
| from json import JSONDecodeError | |
| from textwrap import dedent | |
| import argparse | |
| import json | |
| import sys | |
| import traceback | |
| import warnings | |
| try: | |
| from pkgutil import resolve_name | |
| except ImportError: | |
| from pkgutil_resolve_name import resolve_name # type: ignore[no-redef] | |
| from attrs import define, field | |
| from jsonschema.exceptions import SchemaError | |
| from jsonschema.validators import _RefResolver, validator_for | |
| warnings.warn( | |
| ( | |
| "The jsonschema CLI is deprecated and will be removed in a future " | |
| "version. Please use check-jsonschema instead, which can be installed " | |
| "from https://pypi.org/project/check-jsonschema/" | |
| ), | |
| DeprecationWarning, | |
| stacklevel=2, | |
| ) | |
| class _CannotLoadFile(Exception): | |
| pass | |
| class _Outputter: | |
| _formatter = field() | |
| _stdout = field() | |
| _stderr = field() | |
| def from_arguments(cls, arguments, stdout, stderr): | |
| if arguments["output"] == "plain": | |
| formatter = _PlainFormatter(arguments["error_format"]) | |
| elif arguments["output"] == "pretty": | |
| formatter = _PrettyFormatter() | |
| return cls(formatter=formatter, stdout=stdout, stderr=stderr) | |
| def load(self, path): | |
| try: | |
| file = open(path) # noqa: SIM115, PTH123 | |
| except FileNotFoundError as error: | |
| self.filenotfound_error(path=path, exc_info=sys.exc_info()) | |
| raise _CannotLoadFile() from error | |
| with file: | |
| try: | |
| return json.load(file) | |
| except JSONDecodeError as error: | |
| self.parsing_error(path=path, exc_info=sys.exc_info()) | |
| raise _CannotLoadFile() from error | |
| def filenotfound_error(self, **kwargs): | |
| self._stderr.write(self._formatter.filenotfound_error(**kwargs)) | |
| def parsing_error(self, **kwargs): | |
| self._stderr.write(self._formatter.parsing_error(**kwargs)) | |
| def validation_error(self, **kwargs): | |
| self._stderr.write(self._formatter.validation_error(**kwargs)) | |
| def validation_success(self, **kwargs): | |
| self._stdout.write(self._formatter.validation_success(**kwargs)) | |
| class _PrettyFormatter: | |
| _ERROR_MSG = dedent( | |
| """\ | |
| ===[{type}]===({path})=== | |
| {body} | |
| ----------------------------- | |
| """, | |
| ) | |
| _SUCCESS_MSG = "===[SUCCESS]===({path})===\n" | |
| def filenotfound_error(self, path, exc_info): | |
| return self._ERROR_MSG.format( | |
| path=path, | |
| type="FileNotFoundError", | |
| body=f"{path!r} does not exist.", | |
| ) | |
| def parsing_error(self, path, exc_info): | |
| exc_type, exc_value, exc_traceback = exc_info | |
| exc_lines = "".join( | |
| traceback.format_exception(exc_type, exc_value, exc_traceback), | |
| ) | |
| return self._ERROR_MSG.format( | |
| path=path, | |
| type=exc_type.__name__, | |
| body=exc_lines, | |
| ) | |
| def validation_error(self, instance_path, error): | |
| return self._ERROR_MSG.format( | |
| path=instance_path, | |
| type=error.__class__.__name__, | |
| body=error, | |
| ) | |
| def validation_success(self, instance_path): | |
| return self._SUCCESS_MSG.format(path=instance_path) | |
| class _PlainFormatter: | |
| _error_format = field() | |
| def filenotfound_error(self, path, exc_info): | |
| return f"{path!r} does not exist.\n" | |
| def parsing_error(self, path, exc_info): | |
| return "Failed to parse {}: {}\n".format( | |
| "<stdin>" if path == "<stdin>" else repr(path), | |
| exc_info[1], | |
| ) | |
| def validation_error(self, instance_path, error): | |
| return self._error_format.format(file_name=instance_path, error=error) | |
| def validation_success(self, instance_path): | |
| return "" | |
| def _resolve_name_with_default(name): | |
| if "." not in name: | |
| name = "jsonschema." + name | |
| return resolve_name(name) | |
| parser = argparse.ArgumentParser( | |
| description="JSON Schema Validation CLI", | |
| ) | |
| parser.add_argument( | |
| "-i", "--instance", | |
| action="append", | |
| dest="instances", | |
| help=""" | |
| a path to a JSON instance (i.e. filename.json) to validate (may | |
| be specified multiple times). If no instances are provided via this | |
| option, one will be expected on standard input. | |
| """, | |
| ) | |
| parser.add_argument( | |
| "-F", "--error-format", | |
| help=""" | |
| the format to use for each validation error message, specified | |
| in a form suitable for str.format. This string will be passed | |
| one formatted object named 'error' for each ValidationError. | |
| Only provide this option when using --output=plain, which is the | |
| default. If this argument is unprovided and --output=plain is | |
| used, a simple default representation will be used. | |
| """, | |
| ) | |
| parser.add_argument( | |
| "-o", "--output", | |
| choices=["plain", "pretty"], | |
| default="plain", | |
| help=""" | |
| an output format to use. 'plain' (default) will produce minimal | |
| text with one line for each error, while 'pretty' will produce | |
| more detailed human-readable output on multiple lines. | |
| """, | |
| ) | |
| parser.add_argument( | |
| "-V", "--validator", | |
| type=_resolve_name_with_default, | |
| help=""" | |
| the fully qualified object name of a validator to use, or, for | |
| validators that are registered with jsonschema, simply the name | |
| of the class. | |
| """, | |
| ) | |
| parser.add_argument( | |
| "--base-uri", | |
| help=""" | |
| a base URI to assign to the provided schema, even if it does not | |
| declare one (via e.g. $id). This option can be used if you wish to | |
| resolve relative references to a particular URI (or local path) | |
| """, | |
| ) | |
| parser.add_argument( | |
| "--version", | |
| action="version", | |
| version=metadata.version("jsonschema"), | |
| ) | |
| parser.add_argument( | |
| "schema", | |
| help="the path to a JSON Schema to validate with (i.e. schema.json)", | |
| ) | |
| def parse_args(args): # noqa: D103 | |
| arguments = vars(parser.parse_args(args=args or ["--help"])) | |
| if arguments["output"] != "plain" and arguments["error_format"]: | |
| raise parser.error( | |
| "--error-format can only be used with --output plain", | |
| ) | |
| if arguments["output"] == "plain" and arguments["error_format"] is None: | |
| arguments["error_format"] = "{error.instance}: {error.message}\n" | |
| return arguments | |
| def _validate_instance(instance_path, instance, validator, outputter): | |
| invalid = False | |
| for error in validator.iter_errors(instance): | |
| invalid = True | |
| outputter.validation_error(instance_path=instance_path, error=error) | |
| if not invalid: | |
| outputter.validation_success(instance_path=instance_path) | |
| return invalid | |
| def main(args=sys.argv[1:]): # noqa: D103 | |
| sys.exit(run(arguments=parse_args(args=args))) | |
| def run(arguments, stdout=sys.stdout, stderr=sys.stderr, stdin=sys.stdin): # noqa: D103 | |
| outputter = _Outputter.from_arguments( | |
| arguments=arguments, | |
| stdout=stdout, | |
| stderr=stderr, | |
| ) | |
| try: | |
| schema = outputter.load(arguments["schema"]) | |
| except _CannotLoadFile: | |
| return 1 | |
| Validator = arguments["validator"] | |
| if Validator is None: | |
| Validator = validator_for(schema) | |
| try: | |
| Validator.check_schema(schema) | |
| except SchemaError as error: | |
| outputter.validation_error( | |
| instance_path=arguments["schema"], | |
| error=error, | |
| ) | |
| return 1 | |
| if arguments["instances"]: | |
| load, instances = outputter.load, arguments["instances"] | |
| else: | |
| def load(_): | |
| try: | |
| return json.load(stdin) | |
| except JSONDecodeError as error: | |
| outputter.parsing_error( | |
| path="<stdin>", exc_info=sys.exc_info(), | |
| ) | |
| raise _CannotLoadFile() from error | |
| instances = ["<stdin>"] | |
| resolver = _RefResolver( | |
| base_uri=arguments["base_uri"], | |
| referrer=schema, | |
| ) if arguments["base_uri"] is not None else None | |
| validator = Validator(schema, resolver=resolver) | |
| exit_code = 0 | |
| for each in instances: | |
| try: | |
| instance = load(each) | |
| except _CannotLoadFile: | |
| exit_code = 1 | |
| else: | |
| exit_code |= _validate_instance( | |
| instance_path=each, | |
| instance=instance, | |
| validator=validator, | |
| outputter=outputter, | |
| ) | |
| return exit_code | |