From 484e894601ce5d09e21675f4cd50704954e4caad Mon Sep 17 00:00:00 2001 From: Benjamin Braatz Date: Fri, 3 Sep 2021 23:00:57 +0200 Subject: [PATCH] Use fastjsonschema instead of jsonschema. --- controlpi/__init__.py | 13 +++++------ controlpi/baseplugin.py | 28 +++++++++++------------ controlpi/messagebus.py | 49 ++++++++++++++++++++--------------------- setup.py | 2 +- 4 files changed, 43 insertions(+), 49 deletions(-) diff --git a/controlpi/__init__.py b/controlpi/__init__.py index dfde4e1..78e1ed6 100644 --- a/controlpi/__init__.py +++ b/controlpi/__init__.py @@ -11,7 +11,7 @@ The test function is a utility function to test plugins with minimal boilerplate code. """ import asyncio -import jsonschema # type: ignore +import fastjsonschema # type: ignore from controlpi.messagebus import (MessageBus, BusException, Message, MessageTemplate) @@ -27,13 +27,10 @@ CONF_SCHEMA = {'type': 'object', def _process_conf(message_bus: MessageBus, conf: Dict[str, PluginConf]) -> List[Coroutine]: - jsonschema.Draft7Validator.check_schema(CONF_SCHEMA) - validator = jsonschema.Draft7Validator(CONF_SCHEMA) - valid = True - for error in validator.iter_errors(conf): - print(error) - valid = False - if not valid: + try: + conf = fastjsonschema.validate(CONF_SCHEMA, conf) + except fastjsonschema.JsonSchemaException as e: + print(f"Configuration not valid:\n{e.message}") return [] plugins = PluginRegistry('controlpi_plugins', BasePlugin) coroutines = [message_bus.run()] diff --git a/controlpi/baseplugin.py b/controlpi/baseplugin.py index c803462..72e85f5 100644 --- a/controlpi/baseplugin.py +++ b/controlpi/baseplugin.py @@ -83,11 +83,11 @@ __pdoc__ = {'BasePlugin.CONF_SCHEMA': False} from abc import ABC, abstractmethod import asyncio -import jsonschema # type: ignore +import fastjsonschema # type: ignore from controlpi.messagebus import MessageBus -from typing import Union, Dict, List, Any, Optional +from typing import Union, Dict, List, Any, Optional, Callable JSONSchema = Union[bool, Dict[str, Union[None, str, int, float, bool, Dict[str, Any], List[Any]]]] # Could be more specific. @@ -158,26 +158,24 @@ class BasePlugin(ABC): CONF_SCHEMA: JSONSchema = False - _validator: Optional[jsonschema.Draft7Validator] = None + _validate: Optional[Callable[[PluginConf], PluginConf]] = None def __init__(self, bus: MessageBus, name: str, conf: PluginConf) -> None: # noqa: D107 self.bus = bus self.name = name - if not type(self)._validator: - jsonschema.Draft7Validator.check_schema(type(self).CONF_SCHEMA) - type(self)._validator = \ - jsonschema.Draft7Validator(type(self).CONF_SCHEMA) - validator = type(self)._validator - assert isinstance(validator, jsonschema.Draft7Validator) - valid = True - for error in validator.iter_errors(conf): - print(error) - valid = False - if not valid: + if not type(self)._validate: + type(self)._validate = \ + fastjsonschema.compile(type(self).CONF_SCHEMA) + self.conf = {} + validate = type(self)._validate + assert validate is not None + try: + self.conf = validate(conf) + except fastjsonschema.JsonSchemaException as e: + print(e.message) raise ConfException(f"Configuration for '{self.name}'" " is not valid.") - self.conf = conf self.process_conf() @abstractmethod diff --git a/controlpi/messagebus.py b/controlpi/messagebus.py index d88707a..4ff3453 100644 --- a/controlpi/messagebus.py +++ b/controlpi/messagebus.py @@ -83,7 +83,7 @@ Client 1: {'sender': '', 'target': 'Client 1'} """ import asyncio import json -import jsonschema # type: ignore +import fastjsonschema # type: ignore import sys from typing import Union, Dict, List, Any, Iterable, Callable, Coroutine @@ -98,37 +98,34 @@ JSONSchema = Union[bool, Dict[str, MessageValue]] MessageCallback = Callable[['Message'], Coroutine[Any, Any, None]] -# Global cache of JSON schema validators: -_validators: Dict[str, jsonschema.Draft7Validator] = {} -# Draft7Validator is hardcoded, because _LATEST_VERSION is non-public in -# jsonschema and we also perhaps do not want to upgrade automatically. +# Global cache of JSON schema validation functions: +_validates: Dict[str, Callable[[MessageValue], MessageValue]] = {} -def check_schema(schema: JSONSchema) -> bool: - """Check if the given JSON schema is valid.""" - try: - jsonschema.Draft7Validator.check_schema(schema) - except jsonschema.exceptions.SchemaError: - return False - return True - - -def register_schema(schema: JSONSchema) -> None: +def register_schema(schema: JSONSchema) -> bool: """Register the given JSON schema in the global cache.""" - global _validators + global _validates schema_string = json.dumps(schema) - if schema_string not in _validators: - _validators[schema_string] = jsonschema.Draft7Validator(schema) + if schema_string not in _validates: + if not (isinstance(schema, dict) or isinstance(schema, bool)): + return False + try: + _validates[schema_string] = fastjsonschema.compile(schema) + except fastjsonschema.JsonSchemaDefinitionException: + return False + return True def validate(schema_string: str, value: MessageValue) -> bool: """Validate the given MessageValue against the given JSON schema string.""" - global _validators - if schema_string not in _validators: + global _validates + if schema_string not in _validates: schema = json.loads(schema_string) - _validators[schema_string] = jsonschema.Draft7Validator(schema) - validator = _validators[schema_string] - for error in validator.iter_errors(value): + _validates[schema_string] = fastjsonschema.compile(schema) + validate = _validates[schema_string] + try: + validate(value) + except fastjsonschema.JsonSchemaException: return False return True @@ -458,14 +455,14 @@ class MessageTemplate(Dict[str, JSONSchema]): ... TypeError: 'schema' is not a valid value in MessageTemplate (not a valid JSON schema). + >>> t['key'] = True """ if not isinstance(key, str): raise TypeError(f"'{key}' is not a valid key in MessageTemplate" " (not a string).") - if not check_schema(value): + if not register_schema(value): raise TypeError(f"'{value}' is not a valid value in" " MessageTemplate (not a valid JSON schema).") - register_schema(value) super().__setitem__(key, value) def update(self, *args, **kwargs) -> None: @@ -491,6 +488,7 @@ class MessageTemplate(Dict[str, JSONSchema]): ... TypeError: 'schema' is not a valid value in MessageTemplate (not a valid JSON schema). + >>> t.update({'key': True}) This is also used in __init__: >>> t = MessageTemplate({'key 1': {'const': 'value'}, @@ -514,6 +512,7 @@ class MessageTemplate(Dict[str, JSONSchema]): ... TypeError: 'schema' is not a valid value in MessageTemplate (not a valid JSON schema). + >>> t = MessageTemplate({'key': True}) """ if args: if len(args) > 1: diff --git a/setup.py b/setup.py index 60fa604..fdaee71 100644 --- a/setup.py +++ b/setup.py @@ -16,7 +16,7 @@ setuptools.setup( package_data={"controlpi": ["py.typed"]}, zip_safe=False, install_requires=[ - "jsonschema", + "fastjsonschema", "pyinotify", ], extras_require={ -- 2.34.1