json_schema.py 2.1 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677
  1. """Functions that help us generate and use info.json files.
  2. """
  3. import json
  4. from collections.abc import Mapping
  5. from pathlib import Path
  6. import hjson
  7. import jsonschema
  8. from milc import cli
  9. def json_load(json_file):
  10. """Load a json file from disk.
  11. Note: file must be a Path object.
  12. """
  13. try:
  14. return hjson.load(json_file.open(encoding='utf-8'))
  15. except (json.decoder.JSONDecodeError, hjson.HjsonDecodeError) as e:
  16. cli.log.error('Invalid JSON encountered attempting to load {fg_cyan}%s{fg_reset}:\n\t{fg_red}%s', json_file, e)
  17. exit(1)
  18. except Exception as e:
  19. cli.log.error('Unknown error attempting to load {fg_cyan}%s{fg_reset}:\n\t{fg_red}%s', json_file, e)
  20. exit(1)
  21. def load_jsonschema(schema_name):
  22. """Read a jsonschema file from disk.
  23. """
  24. if Path(schema_name).exists():
  25. return json_load(schema_name)
  26. schema_path = Path(f'data/schemas/{schema_name}.jsonschema')
  27. if not schema_path.exists():
  28. schema_path = Path('data/schemas/false.jsonschema')
  29. return json_load(schema_path)
  30. def create_validator(schema):
  31. """Creates a validator for the given schema id.
  32. """
  33. schema_store = {}
  34. for schema_file in Path('data/schemas').glob('*.jsonschema'):
  35. schema_data = load_jsonschema(schema_file)
  36. if not isinstance(schema_data, dict):
  37. cli.log.debug('Skipping schema file %s', schema_file)
  38. continue
  39. schema_store[schema_data['$id']] = schema_data
  40. resolver = jsonschema.RefResolver.from_schema(schema_store['qmk.keyboard.v1'], store=schema_store)
  41. return jsonschema.Draft7Validator(schema_store[schema], resolver=resolver).validate
  42. def validate(data, schema):
  43. """Validates data against a schema.
  44. """
  45. validator = create_validator(schema)
  46. return validator(data)
  47. def deep_update(origdict, newdict):
  48. """Update a dictionary in place, recursing to do a depth-first deep copy.
  49. """
  50. for key, value in newdict.items():
  51. if isinstance(value, Mapping):
  52. origdict[key] = deep_update(origdict.get(key, {}), value)
  53. else:
  54. origdict[key] = value
  55. return origdict