1
1
import sys
2
-
2
+ from . datapath import DataPath
3
3
from .. import syntax , util
4
4
from .. import validators as val
5
- from yamale .util import YAMALE_SEP
6
5
7
6
# Fix Python 2.x.
8
7
PY2 = sys .version_info [0 ] == 2
@@ -13,123 +12,149 @@ class Schema(object):
13
12
Makes a Schema object from a schema dict.
14
13
Still acts like a dict.
15
14
"""
16
- def __init__ (self , schema_dict , name = '' , validators = None ):
15
+ def __init__ (self , schema_dict , name = '' , validators = None , includes = None ):
17
16
self .validators = validators or val .DefaultValidators
18
17
self .dict = schema_dict
19
18
self .name = name
20
- self ._schema = self ._process_schema (schema_dict , self .validators )
21
- self .includes = {}
19
+ self ._schema = self ._process_schema (DataPath (),
20
+ schema_dict ,
21
+ self .validators )
22
+ # if this schema is included it shares the includes with the top level
23
+ # schema
24
+ self .includes = {} if includes is None else includes
22
25
23
26
def add_include (self , type_dict ):
24
27
for include_name , custom_type in type_dict .items ():
25
28
t = Schema (custom_type , name = include_name ,
26
- validators = self .validators )
29
+ validators = self .validators , includes = self . includes )
27
30
self .includes [include_name ] = t
28
31
29
- def __getitem__ (self , key ):
30
- return self ._schema [key ]
31
-
32
- def _process_schema (self , schema_dict , validators ):
32
+ def _process_schema (self , path , schema_data , validators ):
33
33
"""
34
34
Go through a schema and construct validators.
35
35
"""
36
- schema_flat = util .flatten (schema_dict )
37
-
38
- for key , expression in schema_flat .items ():
39
- try :
40
- schema_flat [key ] = syntax .parse (expression , validators )
41
- except SyntaxError as e :
42
- # Tack on some more context and rethrow.
43
- error = str (e ) + ' at node \' %s\' ' % key
44
- raise SyntaxError (error )
45
- return schema_flat
46
-
47
- def validate (self , data ):
48
- errors = []
49
-
50
- for key , validator in self ._schema .items ():
51
- errors += self ._validate (validator , data , key = key , includes = self .includes )
36
+ if util .is_map (schema_data ) or util .is_list (schema_data ):
37
+ for key , data in util .get_iter (schema_data ):
38
+ schema_data [key ] = self ._process_schema (path + DataPath (key ),
39
+ data ,
40
+ validators )
41
+ else :
42
+ schema_data = self ._parse_schema_item (path ,
43
+ schema_data ,
44
+ validators )
45
+ return schema_data
46
+
47
+ def _parse_schema_item (self , path , expression , validators ):
48
+ try :
49
+ return syntax .parse (expression , validators )
50
+ except SyntaxError as e :
51
+ # Tack on some more context and rethrow.
52
+ error = str (e ) + ' at node \' %s\' ' % str (path )
53
+ raise SyntaxError (error )
54
+
55
+ def validate (self , data , data_name , strict ):
56
+ path = DataPath ()
57
+ errors = self ._validate (self ._schema , data , path , strict )
52
58
53
59
if errors :
54
- header = '\n Error validating data %s with schema %s' % (data .name , self .name )
60
+ header = '\n Error validating data %s with schema %s' % (data_name ,
61
+ self .name )
55
62
error_str = header + '\n \t ' + '\n \t ' .join (errors )
56
63
if PY2 :
57
64
error_str = error_str .encode ('utf-8' )
58
65
raise ValueError (error_str )
59
66
60
- def _validate (self , validator , data , key , position = None , includes = None ):
67
+ def _validate_item (self , validator , data , path , strict , key ):
61
68
"""
62
- Run through a schema and a data structure,
63
- validating along the way.
64
-
65
- Ignores fields that are in the data structure, but not in the schema.
69
+ Fetch item from data at the postion key and validate with validator.
66
70
67
71
Returns an array of errors.
68
72
"""
69
73
errors = []
70
-
71
- if position :
72
- position = '%s%s%s' % (position , util .YAMALE_SEP , key )
73
- else :
74
- position = key
75
-
74
+ path = path + DataPath (key )
76
75
try : # Pull value out of data. Data can be a map or a list/sequence
77
- data_item = util . get_value ( data , key )
76
+ data_item = data [ key ]
78
77
except KeyError : # Oops, that field didn't exist.
79
- if validator .is_optional : # Optional? Who cares.
78
+ # Optional? Who cares.
79
+ if isinstance (validator , val .Validator ) and validator .is_optional :
80
80
return errors
81
81
# SHUT DOWN EVERTYHING
82
- errors .append ('%s: Required field missing' % position . replace ( util . YAMALE_SEP , '.' ) )
82
+ errors .append ('%s: Required field missing' % path )
83
83
return errors
84
84
85
- return self ._validate_item (validator , data_item , position , includes )
85
+ return self ._validate (validator , data_item , path , strict )
86
86
87
- def _validate_item (self , validator , data_item , position , includes ):
87
+ def _validate (self , validator , data , path , strict ):
88
88
"""
89
- Validates a single data item against validator.
89
+ Validate data with validator.
90
+ Special handling of non-primitive validators.
90
91
91
92
Returns an array of errors.
92
93
"""
93
- errors = []
94
94
95
+ if util .is_list (validator ) or util .is_map (validator ):
96
+ return self ._validate_static_map_list (validator ,
97
+ data ,
98
+ path ,
99
+ strict )
100
+
101
+ errors = []
95
102
# Optional field with optional value? Who cares.
96
- if data_item is None and validator .is_optional and validator .can_be_none :
103
+ if (data is None and
104
+ validator .is_optional and
105
+ validator .can_be_none ):
97
106
return errors
98
107
99
- errors += self ._validate_primitive (validator , data_item , position )
108
+ errors += self ._validate_primitive (validator , data , path )
100
109
101
110
if errors :
102
111
return errors
103
112
104
113
if isinstance (validator , val .Include ):
105
- errors += self ._validate_include (validator , data_item ,
106
- includes , position )
114
+ errors += self ._validate_include (validator , data , path , strict )
107
115
108
116
elif isinstance (validator , (val .Map , val .List )):
109
- errors += self ._validate_map_list (validator , data_item ,
110
- includes , position )
117
+ errors += self ._validate_map_list (validator , data , path , strict )
111
118
112
119
elif isinstance (validator , val .Any ):
113
- errors += self ._validate_any (validator , data_item ,
114
- includes , position )
120
+ errors += self ._validate_any (validator , data , path , strict )
115
121
116
122
return errors
117
123
118
- def _validate_map_list (self , validator , data , includes , pos ):
124
+ def _validate_static_map_list (self , validator , data , path , strict ):
125
+ if util .is_map (validator ) and not util .is_map (data ):
126
+ return ["%s : '%s' is not a map" % (path , data )]
127
+
128
+ if util .is_list (validator ) and not util .is_list (data ):
129
+ return ["%s : '%s' is not a list" % (path , data )]
130
+
131
+ errors = []
132
+
133
+ if strict :
134
+ data_keys = set (util .get_keys (data ))
135
+ validator_keys = set (util .get_keys (validator ))
136
+ for key in data_keys - validator_keys :
137
+ error_path = path + DataPath (key )
138
+ errors += ['%s: Unexpected element' % error_path ]
139
+
140
+ for key , sub_validator in util .get_iter (validator ):
141
+ errors += self ._validate_item (sub_validator ,
142
+ data ,
143
+ path ,
144
+ strict ,
145
+ key )
146
+ return errors
147
+
148
+ def _validate_map_list (self , validator , data , path , strict ):
119
149
errors = []
120
150
121
151
if not validator .validators :
122
152
return errors # No validators, user just wanted a map.
123
153
124
- if isinstance (validator , val .List ):
125
- keys = range (len (data ))
126
- else :
127
- keys = data .keys ()
128
-
129
- for key in keys :
154
+ for key in util .get_keys (data ):
130
155
sub_errors = []
131
156
for v in validator .validators :
132
- err = self ._validate (v , data , key , pos , includes )
157
+ err = self ._validate_item (v , data , path , strict , key )
133
158
if err :
134
159
sub_errors .append (err )
135
160
@@ -140,21 +165,18 @@ def _validate_map_list(self, validator, data, includes, pos):
140
165
141
166
return errors
142
167
143
- def _validate_include (self , validator , data , includes , pos ):
144
- errors = []
145
-
146
- include_schema = includes .get (validator .include_name )
168
+ def _validate_include (self , validator , data , path , strict ):
169
+ include_schema = self .includes .get (validator .include_name )
147
170
if not include_schema :
148
- errors .append ('Include \' %s\' has not been defined.' % validator .include_name )
149
- return errors
150
-
151
- for key , validator in include_schema ._schema .items ():
152
- errors += include_schema ._validate (
153
- validator , data , includes = includes , key = key , position = pos )
154
-
155
- return errors
156
-
157
- def _validate_any (self , validator , data , includes , pos ):
171
+ return [('Include \' %s\' has not been defined.'
172
+ % validator .include_name )]
173
+ strict = strict if validator .strict is None else validator .strict
174
+ return include_schema ._validate (include_schema ._schema ,
175
+ data ,
176
+ path ,
177
+ strict )
178
+
179
+ def _validate_any (self , validator , data , path , strict ):
158
180
errors = []
159
181
160
182
if not validator .validators :
@@ -163,7 +185,7 @@ def _validate_any(self, validator, data, includes, pos):
163
185
164
186
sub_errors = []
165
187
for v in validator .validators :
166
- err = self ._validate_item (v , data , pos , includes )
188
+ err = self ._validate (v , data , path , strict )
167
189
if err :
168
190
sub_errors .append (err )
169
191
@@ -174,10 +196,10 @@ def _validate_any(self, validator, data, includes, pos):
174
196
175
197
return errors
176
198
177
- def _validate_primitive (self , validator , data , pos ):
199
+ def _validate_primitive (self , validator , data , path ):
178
200
errors = validator .validate (data )
179
201
180
202
for i , error in enumerate (errors ):
181
- errors [i ] = '%s: ' % pos . replace ( YAMALE_SEP , '.' ) + error
203
+ errors [i ] = ( '%s: ' % path ) + error
182
204
183
205
return errors
0 commit comments