Coverage for d7a/support/schema.py: 83%
92 statements
« prev ^ index » next coverage.py v7.5.0, created at 2024-05-24 08:03 +0200
« prev ^ index » next coverage.py v7.5.0, created at 2024-05-24 08:03 +0200
1#
2# Copyright (c) 2015-2021 University of Antwerp, Aloxy NV.
3#
4# This file is part of pyd7a.
5# See https://github.com/Sub-IoT/pyd7a for further info.
6#
7# Licensed under the Apache License, Version 2.0 (the "License");
8# you may not use this file except in compliance with the License.
9# You may obtain a copy of the License at
10#
11# http://www.apache.org/licenses/LICENSE-2.0
12#
13# Unless required by applicable law or agreed to in writing, software
14# distributed under the License is distributed on an "AS IS" BASIS,
15# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16# See the License for the specific language governing permissions and
17# limitations under the License.
18#
20# author: Christophe VG <contact@christophe.vg>
21# base class for schema-validatable entity classes
23import math
25from cerberus import Validator
27import inspect
29class ObjectValidator(Validator):
30 def _validate_isinstance(self, clazz, field, value):
31 """ {'nullable': True } """ # dummy validation schema to avoid warning ;-)
32 if value is None:
33 # it is checked in validator.py if nullable is allowed, if not it will return error earlier
34 return
35 if not isinstance(value, clazz):
36 self._error(field, "Should be instance of " + clazz.__name__)
38 def _validate_allowedmembers(self, allowed_members, field, value):
39 """ {'nullable': True } """ # dummy validation schema to avoid warning ;-)
40 for allowed_member in allowed_members:
41 if value == allowed_member:
42 return
44 self._error(field, "Only following enum values allowed: " + ", ".join([m.name for m in allowed_members]))
47class Validatable(object):
48 def __init__(self):
49 self.validate()
51 def as_dict(self):
52 d = { "__CLASS__" : self.__class__.__name__ }
53 for k, v in list(self.__dict__.items()):
54 if inspect.isclass(v): continue # skip classes
55 if isinstance(v, list):
56 l = []
57 for i in v:
58 if isinstance(i, Validatable):
59 l.append(i.as_dict())
60 else:
61 l.append(i)
62 d[k] = l
63 elif isinstance(v, Validatable):
64 d[k] = v.as_dict()
65 else:
66 d[k] = v
67 return d
69 SCHEMA = []
71 def validate(self):
72 validator = ObjectValidator(
73 { "item": { "oneof_schema" : self.SCHEMA, "type": "dict"} },
74 allow_unknown=True
75 )
76 obj_dict = {}
77 for attr in dir(self): obj_dict[attr] = getattr(self, attr)
78 if not validator.validate({ "item" : obj_dict }):
79 try:
80 errors = validator.errors # cerberus returns NotImplementedError in some cases (for now) ...
81 except NotImplementedError:
82 errors = None
84 raise ValueError(errors)
86class Types(object):
87 @staticmethod
88 def BOOLEAN(value=None, nullable=False):
89 b = { "type": "boolean", "nullable": nullable }
90 if value is not None: b["allowed"] = [value]
91 return b
93 @staticmethod
94 def BYTE():
95 return { "type": "integer", "nullable": False, "min": 0, "max": 0xFF }
97 @staticmethod
98 def STRING(maxlength=None):
99 s = { "type": "string" }
100 if maxlength is not None:
101 s["maxlength"] = maxlength
103 return s
105 @staticmethod
106 def BYTES(nullable=False):
107 return {
108 "type": "list",
109 "schema": { "type": "integer", "min": 0, "max": 0xFF},
110 "nullable": nullable
111 }
113 @staticmethod
114 def OBJECT(clazz=None, nullable=False):
115 o = { "nullable": nullable }
116 if clazz is not None: o["isinstance"] = clazz
117 return o
119 @staticmethod
120 def INTEGER(values=None, min=None, max=None, nullable=False):
121 i = { "type": "integer", "nullable": nullable }
122 if min is not None: i["min"] = min
123 if max is not None: i["max"] = max
124 if values is not None:
125 i["allowed"] = values
126 if None in values: i["nullable"] = True
127 return i
129 @staticmethod
130 def ENUM(type, allowedvalues=None, nullable=False):
131 e = {"isinstance": type }
132 if allowedvalues is not None: e["allowedmembers"] = allowedvalues
133 return e
134 #
135 # e = { "type": "integer", "allowed" : values}
136 # if None in values: e["nullable"] = True
137 # return e
139 @staticmethod
140 def BITS(length, min=0x0, max=None):
141 max = max if max is not None else math.pow(2, length)-1
142 return { "type": "integer", "min": 0x0, "max": max }
144 @staticmethod
145 def LIST(type=None, minlength=0, maxlength=None):
146 l = { "type" : "list", "minlength": minlength }
147 if type:
148 l["schema"] = {
149 "isinstance" : type
150 }
151 if maxlength:
152 l["maxlength"] = maxlength
154 return l