##// END OF EJS Templates
validators: shortener factory for grouping hash
ergo -
Show More
@@ -1,773 +1,773 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2016 RhodeCode GmbH
3 # Copyright (C) 2010-2016 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # AppEnlight Enterprise Edition, including its added features, Support
18 # AppEnlight Enterprise Edition, including its added features, Support
19 # services, and proprietary license terms, please see
19 # services, and proprietary license terms, please see
20 # https://rhodecode.com/licenses/
20 # https://rhodecode.com/licenses/
21
21
22 import datetime
22 import datetime
23
23
24 import colander
24 import colander
25 from colander import null
25 from colander import null
26
26
27 # those keywords are here so we can distingush between searching for tags and
27 # those keywords are here so we can distingush between searching for tags and
28 # normal properties of reports/logs
28 # normal properties of reports/logs
29 accepted_search_params = ['resource',
29 accepted_search_params = ['resource',
30 'request_id',
30 'request_id',
31 'start_date',
31 'start_date',
32 'end_date',
32 'end_date',
33 'page',
33 'page',
34 'min_occurences',
34 'min_occurences',
35 'http_status',
35 'http_status',
36 'priority',
36 'priority',
37 'error',
37 'error',
38 'url_path',
38 'url_path',
39 'url_domain',
39 'url_domain',
40 'report_status',
40 'report_status',
41 'min_duration',
41 'min_duration',
42 'max_duration',
42 'max_duration',
43 'message',
43 'message',
44 'level',
44 'level',
45 'namespace']
45 'namespace']
46
46
47
47
48 @colander.deferred
48 @colander.deferred
49 def deferred_utcnow(node, kw):
49 def deferred_utcnow(node, kw):
50 return kw['utcnow']
50 return kw['utcnow']
51
51
52
52
53 @colander.deferred
53 @colander.deferred
54 def optional_limited_date(node, kw):
54 def optional_limited_date(node, kw):
55 if not kw.get('allow_permanent_storage'):
55 if not kw.get('allow_permanent_storage'):
56 return limited_date
56 return limited_date
57
57
58
58
59 def lowercase_preparer(input_data):
59 def lowercase_preparer(input_data):
60 """
60 """
61 Transforms a list of string entries to lowercase
61 Transforms a list of string entries to lowercase
62 Used in search query validation
62 Used in search query validation
63 """
63 """
64 if not input_data:
64 if not input_data:
65 return input_data
65 return input_data
66 return [x.lower() for x in input_data]
66 return [x.lower() for x in input_data]
67
67
68
68
69 def shortener_factory(cutoff_size=32):
69 def shortener_factory(cutoff_size=32):
70 """
70 """
71 Limits the input data to specific character count
71 Limits the input data to specific character count
72 :arg cutoff_cutoff_size How much characters to store
72 :arg cutoff_cutoff_size How much characters to store
73
73
74 """
74 """
75
75
76 def shortener(input_data):
76 def shortener(input_data):
77 if not input_data:
77 if not input_data:
78 return input_data
78 return input_data
79 else:
79 else:
80 if isinstance(input_data, str):
80 if isinstance(input_data, str):
81 return input_data[:cutoff_size]
81 return input_data[:cutoff_size]
82 else:
82 else:
83 return input_data
83 return input_data
84
84
85 return shortener
85 return shortener
86
86
87
87
88 def cast_to_unicode_or_null(value):
88 def cast_to_unicode_or_null(value):
89 if value is not colander.null:
89 if value is not colander.null:
90 return str(value)
90 return str(value)
91 return None
91 return None
92
92
93
93
94 class NonTZDate(colander.DateTime):
94 class NonTZDate(colander.DateTime):
95 """ Returns null for incorrect date format - also removes tz info"""
95 """ Returns null for incorrect date format - also removes tz info"""
96
96
97 def deserialize(self, node, cstruct):
97 def deserialize(self, node, cstruct):
98 # disabled for now
98 # disabled for now
99 # if cstruct and isinstance(cstruct, str):
99 # if cstruct and isinstance(cstruct, str):
100 # if ':' not in cstruct:
100 # if ':' not in cstruct:
101 # cstruct += ':0.0'
101 # cstruct += ':0.0'
102 # if '.' not in cstruct:
102 # if '.' not in cstruct:
103 # cstruct += '.0'
103 # cstruct += '.0'
104 value = super(NonTZDate, self).deserialize(node, cstruct)
104 value = super(NonTZDate, self).deserialize(node, cstruct)
105 if value:
105 if value:
106 return value.replace(tzinfo=None)
106 return value.replace(tzinfo=None)
107 return value
107 return value
108
108
109
109
110 class UnknownType(object):
110 class UnknownType(object):
111 """
111 """
112 Universal type that will accept a deserialized JSON object and store it unaltered
112 Universal type that will accept a deserialized JSON object and store it unaltered
113 """
113 """
114
114
115 def serialize(self, node, appstruct):
115 def serialize(self, node, appstruct):
116 if appstruct is null:
116 if appstruct is null:
117 return null
117 return null
118 return appstruct
118 return appstruct
119
119
120 def deserialize(self, node, cstruct):
120 def deserialize(self, node, cstruct):
121 if cstruct is null:
121 if cstruct is null:
122 return null
122 return null
123 return cstruct
123 return cstruct
124
124
125 def cstruct_children(self):
125 def cstruct_children(self):
126 return []
126 return []
127
127
128
128
129 # SLOW REPORT SCHEMA
129 # SLOW REPORT SCHEMA
130
130
131 def rewrite_type(input_data):
131 def rewrite_type(input_data):
132 """
132 """
133 Fix for legacy appenlight clients
133 Fix for legacy appenlight clients
134 """
134 """
135 if input_data == 'remote_call':
135 if input_data == 'remote_call':
136 return 'remote'
136 return 'remote'
137 return input_data
137 return input_data
138
138
139
139
140 class ExtraTupleSchema(colander.TupleSchema):
140 class ExtraTupleSchema(colander.TupleSchema):
141 name = colander.SchemaNode(colander.String(),
141 name = colander.SchemaNode(colander.String(),
142 validator=colander.Length(1, 64))
142 validator=colander.Length(1, 64))
143 value = colander.SchemaNode(UnknownType(),
143 value = colander.SchemaNode(UnknownType(),
144 preparer=shortener_factory(512),
144 preparer=shortener_factory(512),
145 missing=None)
145 missing=None)
146
146
147
147
148 class ExtraSchemaList(colander.SequenceSchema):
148 class ExtraSchemaList(colander.SequenceSchema):
149 tag = ExtraTupleSchema()
149 tag = ExtraTupleSchema()
150 missing = None
150 missing = None
151
151
152
152
153 class TagsTupleSchema(colander.TupleSchema):
153 class TagsTupleSchema(colander.TupleSchema):
154 name = colander.SchemaNode(colander.String(),
154 name = colander.SchemaNode(colander.String(),
155 validator=colander.Length(1, 128))
155 validator=colander.Length(1, 128))
156 value = colander.SchemaNode(UnknownType(),
156 value = colander.SchemaNode(UnknownType(),
157 preparer=shortener_factory(128),
157 preparer=shortener_factory(128),
158 missing=None)
158 missing=None)
159
159
160
160
161 class TagSchemaList(colander.SequenceSchema):
161 class TagSchemaList(colander.SequenceSchema):
162 tag = TagsTupleSchema()
162 tag = TagsTupleSchema()
163 missing = None
163 missing = None
164
164
165
165
166 class NumericTagsTupleSchema(colander.TupleSchema):
166 class NumericTagsTupleSchema(colander.TupleSchema):
167 name = colander.SchemaNode(colander.String(),
167 name = colander.SchemaNode(colander.String(),
168 validator=colander.Length(1, 128))
168 validator=colander.Length(1, 128))
169 value = colander.SchemaNode(colander.Float(), missing=0)
169 value = colander.SchemaNode(colander.Float(), missing=0)
170
170
171
171
172 class NumericTagSchemaList(colander.SequenceSchema):
172 class NumericTagSchemaList(colander.SequenceSchema):
173 tag = NumericTagsTupleSchema()
173 tag = NumericTagsTupleSchema()
174 missing = None
174 missing = None
175
175
176
176
177 class SlowCallSchema(colander.MappingSchema):
177 class SlowCallSchema(colander.MappingSchema):
178 """
178 """
179 Validates slow call format in slow call list
179 Validates slow call format in slow call list
180 """
180 """
181 start = colander.SchemaNode(NonTZDate())
181 start = colander.SchemaNode(NonTZDate())
182 end = colander.SchemaNode(NonTZDate())
182 end = colander.SchemaNode(NonTZDate())
183 statement = colander.SchemaNode(colander.String(), missing='')
183 statement = colander.SchemaNode(colander.String(), missing='')
184 parameters = colander.SchemaNode(UnknownType(), missing=None)
184 parameters = colander.SchemaNode(UnknownType(), missing=None)
185 type = colander.SchemaNode(
185 type = colander.SchemaNode(
186 colander.String(),
186 colander.String(),
187 preparer=rewrite_type,
187 preparer=rewrite_type,
188 validator=colander.OneOf(
188 validator=colander.OneOf(
189 ['tmpl', 'sql', 'nosql', 'remote', 'unknown', 'custom']),
189 ['tmpl', 'sql', 'nosql', 'remote', 'unknown', 'custom']),
190 missing='unknown')
190 missing='unknown')
191 subtype = colander.SchemaNode(colander.String(),
191 subtype = colander.SchemaNode(colander.String(),
192 validator=colander.Length(1, 16),
192 validator=colander.Length(1, 16),
193 missing='unknown')
193 missing='unknown')
194 location = colander.SchemaNode(colander.String(),
194 location = colander.SchemaNode(colander.String(),
195 validator=colander.Length(1, 255),
195 validator=colander.Length(1, 255),
196 missing='')
196 missing='')
197
197
198
198
199 def limited_date(node, value):
199 def limited_date(node, value):
200 """ checks to make sure that the value is not older/newer than 2h """
200 """ checks to make sure that the value is not older/newer than 2h """
201 past_hours = 72
201 past_hours = 72
202 future_hours = 2
202 future_hours = 2
203 min_time = datetime.datetime.utcnow() - datetime.timedelta(
203 min_time = datetime.datetime.utcnow() - datetime.timedelta(
204 hours=past_hours)
204 hours=past_hours)
205 max_time = datetime.datetime.utcnow() + datetime.timedelta(
205 max_time = datetime.datetime.utcnow() + datetime.timedelta(
206 hours=future_hours)
206 hours=future_hours)
207 if min_time > value:
207 if min_time > value:
208 msg = '%r is older from current UTC time by ' + str(past_hours)
208 msg = '%r is older from current UTC time by ' + str(past_hours)
209 msg += ' hours. Ask administrator to enable permanent logging for ' \
209 msg += ' hours. Ask administrator to enable permanent logging for ' \
210 'your application to store logs with dates in past.'
210 'your application to store logs with dates in past.'
211 raise colander.Invalid(node, msg % value)
211 raise colander.Invalid(node, msg % value)
212 if max_time < value:
212 if max_time < value:
213 msg = '%r is newer from current UTC time by ' + str(future_hours)
213 msg = '%r is newer from current UTC time by ' + str(future_hours)
214 msg += ' hours. Ask administrator to enable permanent logging for ' \
214 msg += ' hours. Ask administrator to enable permanent logging for ' \
215 'your application to store logs with dates in future.'
215 'your application to store logs with dates in future.'
216 raise colander.Invalid(node, msg % value)
216 raise colander.Invalid(node, msg % value)
217
217
218
218
219 class SlowCallListSchema(colander.SequenceSchema):
219 class SlowCallListSchema(colander.SequenceSchema):
220 """
220 """
221 Validates list of individual slow calls
221 Validates list of individual slow calls
222 """
222 """
223 slow_call = SlowCallSchema()
223 slow_call = SlowCallSchema()
224
224
225
225
226 class RequestStatsSchema(colander.MappingSchema):
226 class RequestStatsSchema(colander.MappingSchema):
227 """
227 """
228 Validates format of requests statistics dictionary
228 Validates format of requests statistics dictionary
229 """
229 """
230 main = colander.SchemaNode(colander.Float(), validator=colander.Range(0),
230 main = colander.SchemaNode(colander.Float(), validator=colander.Range(0),
231 missing=0)
231 missing=0)
232 sql = colander.SchemaNode(colander.Float(), validator=colander.Range(0),
232 sql = colander.SchemaNode(colander.Float(), validator=colander.Range(0),
233 missing=0)
233 missing=0)
234 nosql = colander.SchemaNode(colander.Float(), validator=colander.Range(0),
234 nosql = colander.SchemaNode(colander.Float(), validator=colander.Range(0),
235 missing=0)
235 missing=0)
236 remote = colander.SchemaNode(colander.Float(), validator=colander.Range(0),
236 remote = colander.SchemaNode(colander.Float(), validator=colander.Range(0),
237 missing=0)
237 missing=0)
238 tmpl = colander.SchemaNode(colander.Float(), validator=colander.Range(0),
238 tmpl = colander.SchemaNode(colander.Float(), validator=colander.Range(0),
239 missing=0)
239 missing=0)
240 custom = colander.SchemaNode(colander.Float(), validator=colander.Range(0),
240 custom = colander.SchemaNode(colander.Float(), validator=colander.Range(0),
241 missing=0)
241 missing=0)
242 sql_calls = colander.SchemaNode(colander.Float(),
242 sql_calls = colander.SchemaNode(colander.Float(),
243 validator=colander.Range(0),
243 validator=colander.Range(0),
244 missing=0)
244 missing=0)
245 nosql_calls = colander.SchemaNode(colander.Float(),
245 nosql_calls = colander.SchemaNode(colander.Float(),
246 validator=colander.Range(0),
246 validator=colander.Range(0),
247 missing=0)
247 missing=0)
248 remote_calls = colander.SchemaNode(colander.Float(),
248 remote_calls = colander.SchemaNode(colander.Float(),
249 validator=colander.Range(0),
249 validator=colander.Range(0),
250 missing=0)
250 missing=0)
251 tmpl_calls = colander.SchemaNode(colander.Float(),
251 tmpl_calls = colander.SchemaNode(colander.Float(),
252 validator=colander.Range(0),
252 validator=colander.Range(0),
253 missing=0)
253 missing=0)
254 custom_calls = colander.SchemaNode(colander.Float(),
254 custom_calls = colander.SchemaNode(colander.Float(),
255 validator=colander.Range(0),
255 validator=colander.Range(0),
256 missing=0)
256 missing=0)
257
257
258
258
259 class FrameInfoVarSchema(colander.SequenceSchema):
259 class FrameInfoVarSchema(colander.SequenceSchema):
260 """
260 """
261 Validates format of frame variables of a traceback
261 Validates format of frame variables of a traceback
262 """
262 """
263 vars = colander.SchemaNode(UnknownType(),
263 vars = colander.SchemaNode(UnknownType(),
264 validator=colander.Length(2, 2))
264 validator=colander.Length(2, 2))
265
265
266
266
267 class FrameInfoSchema(colander.MappingSchema):
267 class FrameInfoSchema(colander.MappingSchema):
268 """
268 """
269 Validates format of a traceback line
269 Validates format of a traceback line
270 """
270 """
271 cline = colander.SchemaNode(colander.String(), missing='')
271 cline = colander.SchemaNode(colander.String(), missing='')
272 module = colander.SchemaNode(colander.String(), missing='')
272 module = colander.SchemaNode(colander.String(), missing='')
273 line = colander.SchemaNode(colander.String(), missing='')
273 line = colander.SchemaNode(colander.String(), missing='')
274 file = colander.SchemaNode(colander.String(), missing='')
274 file = colander.SchemaNode(colander.String(), missing='')
275 fn = colander.SchemaNode(colander.String(), missing='')
275 fn = colander.SchemaNode(colander.String(), missing='')
276 vars = FrameInfoVarSchema()
276 vars = FrameInfoVarSchema()
277
277
278
278
279 class FrameInfoListSchema(colander.SequenceSchema):
279 class FrameInfoListSchema(colander.SequenceSchema):
280 """
280 """
281 Validates format of list of traceback lines
281 Validates format of list of traceback lines
282 """
282 """
283 frame = colander.SchemaNode(UnknownType())
283 frame = colander.SchemaNode(UnknownType())
284
284
285
285
286 class ReportDetailBaseSchema(colander.MappingSchema):
286 class ReportDetailBaseSchema(colander.MappingSchema):
287 """
287 """
288 Validates format of report - ie. request parameters and stats for a request in report group
288 Validates format of report - ie. request parameters and stats for a request in report group
289 """
289 """
290 username = colander.SchemaNode(colander.String(),
290 username = colander.SchemaNode(colander.String(),
291 preparer=[shortener_factory(255),
291 preparer=[shortener_factory(255),
292 lambda x: x or ''],
292 lambda x: x or ''],
293 missing='')
293 missing='')
294 request_id = colander.SchemaNode(colander.String(),
294 request_id = colander.SchemaNode(colander.String(),
295 preparer=shortener_factory(40),
295 preparer=shortener_factory(40),
296 missing='')
296 missing='')
297 url = colander.SchemaNode(colander.String(),
297 url = colander.SchemaNode(colander.String(),
298 preparer=shortener_factory(1024), missing='')
298 preparer=shortener_factory(1024), missing='')
299 ip = colander.SchemaNode(colander.String(), preparer=shortener_factory(39),
299 ip = colander.SchemaNode(colander.String(), preparer=shortener_factory(39),
300 missing=None)
300 missing=None)
301 start_time = colander.SchemaNode(NonTZDate(),
301 start_time = colander.SchemaNode(NonTZDate(),
302 validator=optional_limited_date,
302 validator=optional_limited_date,
303 missing=deferred_utcnow)
303 missing=deferred_utcnow)
304 end_time = colander.SchemaNode(NonTZDate(),
304 end_time = colander.SchemaNode(NonTZDate(),
305 validator=optional_limited_date,
305 validator=optional_limited_date,
306 missing=None)
306 missing=None)
307 user_agent = colander.SchemaNode(colander.String(),
307 user_agent = colander.SchemaNode(colander.String(),
308 preparer=[shortener_factory(512),
308 preparer=[shortener_factory(512),
309 lambda x: x or ''],
309 lambda x: x or ''],
310 missing='')
310 missing='')
311 message = colander.SchemaNode(colander.String(),
311 message = colander.SchemaNode(colander.String(),
312 preparer=shortener_factory(2048),
312 preparer=shortener_factory(2048),
313 missing='')
313 missing='')
314 group_string = colander.SchemaNode(colander.String(),
314 group_string = colander.SchemaNode(colander.String(),
315 validator=colander.Length(1, 512),
315 preparer=shortener_factory(512),
316 missing=None)
316 missing=None)
317 request_stats = RequestStatsSchema(missing=None)
317 request_stats = RequestStatsSchema(missing=None)
318 request = colander.SchemaNode(colander.Mapping(unknown='preserve'),
318 request = colander.SchemaNode(colander.Mapping(unknown='preserve'),
319 missing={})
319 missing={})
320 traceback = FrameInfoListSchema(missing=None)
320 traceback = FrameInfoListSchema(missing=None)
321 slow_calls = SlowCallListSchema(missing=[])
321 slow_calls = SlowCallListSchema(missing=[])
322 extra = ExtraSchemaList()
322 extra = ExtraSchemaList()
323
323
324
324
325 class ReportDetailSchema_0_5(ReportDetailBaseSchema):
325 class ReportDetailSchema_0_5(ReportDetailBaseSchema):
326 pass
326 pass
327
327
328
328
329 class ReportDetailSchemaPermissiveDate_0_5(ReportDetailSchema_0_5):
329 class ReportDetailSchemaPermissiveDate_0_5(ReportDetailSchema_0_5):
330 start_time = colander.SchemaNode(NonTZDate(), missing=deferred_utcnow)
330 start_time = colander.SchemaNode(NonTZDate(), missing=deferred_utcnow)
331 end_time = colander.SchemaNode(NonTZDate(), missing=None)
331 end_time = colander.SchemaNode(NonTZDate(), missing=None)
332
332
333
333
334 class ReportSchemaBase(colander.MappingSchema):
334 class ReportSchemaBase(colander.MappingSchema):
335 """
335 """
336 Validates format of report group
336 Validates format of report group
337 """
337 """
338 client = colander.SchemaNode(colander.String(),
338 client = colander.SchemaNode(colander.String(),
339 preparer=lambda x: x or 'unknown')
339 preparer=lambda x: x or 'unknown')
340 server = colander.SchemaNode(
340 server = colander.SchemaNode(
341 colander.String(),
341 colander.String(),
342 preparer=[
342 preparer=[
343 lambda x: x.lower() if x else 'unknown', shortener_factory(128)],
343 lambda x: x.lower() if x else 'unknown', shortener_factory(128)],
344 missing='unknown')
344 missing='unknown')
345 priority = colander.SchemaNode(colander.Int(),
345 priority = colander.SchemaNode(colander.Int(),
346 preparer=[lambda x: x or 5],
346 preparer=[lambda x: x or 5],
347 validator=colander.Range(1, 10),
347 validator=colander.Range(1, 10),
348 missing=5)
348 missing=5)
349 language = colander.SchemaNode(colander.String(), missing='unknown')
349 language = colander.SchemaNode(colander.String(), missing='unknown')
350 error = colander.SchemaNode(colander.String(),
350 error = colander.SchemaNode(colander.String(),
351 preparer=shortener_factory(512),
351 preparer=shortener_factory(512),
352 missing='')
352 missing='')
353 view_name = colander.SchemaNode(colander.String(),
353 view_name = colander.SchemaNode(colander.String(),
354 preparer=[shortener_factory(128),
354 preparer=[shortener_factory(128),
355 lambda x: x or ''],
355 lambda x: x or ''],
356 missing='')
356 missing='')
357 http_status = colander.SchemaNode(colander.Int(),
357 http_status = colander.SchemaNode(colander.Int(),
358 preparer=[lambda x: x or 200],
358 preparer=[lambda x: x or 200],
359 validator=colander.Range(1))
359 validator=colander.Range(1))
360
360
361 occurences = colander.SchemaNode(colander.Int(),
361 occurences = colander.SchemaNode(colander.Int(),
362 validator=colander.Range(1, 99999999999),
362 validator=colander.Range(1, 99999999999),
363 missing=1)
363 missing=1)
364 tags = TagSchemaList()
364 tags = TagSchemaList()
365
365
366
366
367 class ReportSchema_0_5(ReportSchemaBase, ReportDetailSchema_0_5):
367 class ReportSchema_0_5(ReportSchemaBase, ReportDetailSchema_0_5):
368 pass
368 pass
369
369
370
370
371 class ReportSchemaPermissiveDate_0_5(ReportSchemaBase,
371 class ReportSchemaPermissiveDate_0_5(ReportSchemaBase,
372 ReportDetailSchemaPermissiveDate_0_5):
372 ReportDetailSchemaPermissiveDate_0_5):
373 pass
373 pass
374
374
375
375
376 class ReportListSchema_0_5(colander.SequenceSchema):
376 class ReportListSchema_0_5(colander.SequenceSchema):
377 """
377 """
378 Validates format of list of report groups
378 Validates format of list of report groups
379 """
379 """
380 report = ReportSchema_0_5()
380 report = ReportSchema_0_5()
381 validator = colander.Length(1)
381 validator = colander.Length(1)
382
382
383
383
384 class ReportListPermissiveDateSchema_0_5(colander.SequenceSchema):
384 class ReportListPermissiveDateSchema_0_5(colander.SequenceSchema):
385 """
385 """
386 Validates format of list of report groups
386 Validates format of list of report groups
387 """
387 """
388 report = ReportSchemaPermissiveDate_0_5()
388 report = ReportSchemaPermissiveDate_0_5()
389 validator = colander.Length(1)
389 validator = colander.Length(1)
390
390
391
391
392 class LogSchema(colander.MappingSchema):
392 class LogSchema(colander.MappingSchema):
393 """
393 """
394 Validates format if individual log entry
394 Validates format if individual log entry
395 """
395 """
396 primary_key = colander.SchemaNode(UnknownType(),
396 primary_key = colander.SchemaNode(UnknownType(),
397 preparer=[cast_to_unicode_or_null,
397 preparer=[cast_to_unicode_or_null,
398 shortener_factory(128)],
398 shortener_factory(128)],
399 missing=None)
399 missing=None)
400 log_level = colander.SchemaNode(colander.String(),
400 log_level = colander.SchemaNode(colander.String(),
401 preparer=shortener_factory(10),
401 preparer=shortener_factory(10),
402 missing='UNKNOWN')
402 missing='UNKNOWN')
403 message = colander.SchemaNode(colander.String(),
403 message = colander.SchemaNode(colander.String(),
404 preparer=shortener_factory(4096),
404 preparer=shortener_factory(4096),
405 missing='')
405 missing='')
406 namespace = colander.SchemaNode(colander.String(),
406 namespace = colander.SchemaNode(colander.String(),
407 preparer=shortener_factory(128),
407 preparer=shortener_factory(128),
408 missing='')
408 missing='')
409 request_id = colander.SchemaNode(colander.String(),
409 request_id = colander.SchemaNode(colander.String(),
410 preparer=shortener_factory(40),
410 preparer=shortener_factory(40),
411 missing='')
411 missing='')
412 server = colander.SchemaNode(colander.String(),
412 server = colander.SchemaNode(colander.String(),
413 preparer=shortener_factory(128),
413 preparer=shortener_factory(128),
414 missing='unknown')
414 missing='unknown')
415 date = colander.SchemaNode(NonTZDate(),
415 date = colander.SchemaNode(NonTZDate(),
416 validator=limited_date,
416 validator=limited_date,
417 missing=deferred_utcnow)
417 missing=deferred_utcnow)
418 tags = TagSchemaList()
418 tags = TagSchemaList()
419
419
420
420
421 class LogSchemaPermanent(LogSchema):
421 class LogSchemaPermanent(LogSchema):
422 date = colander.SchemaNode(NonTZDate(),
422 date = colander.SchemaNode(NonTZDate(),
423 missing=deferred_utcnow)
423 missing=deferred_utcnow)
424 permanent = colander.SchemaNode(colander.Boolean(), missing=False)
424 permanent = colander.SchemaNode(colander.Boolean(), missing=False)
425
425
426
426
427 class LogListSchema(colander.SequenceSchema):
427 class LogListSchema(colander.SequenceSchema):
428 """
428 """
429 Validates format of list of log entries
429 Validates format of list of log entries
430 """
430 """
431 log = LogSchema()
431 log = LogSchema()
432 validator = colander.Length(1)
432 validator = colander.Length(1)
433
433
434
434
435 class LogListPermanentSchema(colander.SequenceSchema):
435 class LogListPermanentSchema(colander.SequenceSchema):
436 """
436 """
437 Validates format of list of log entries
437 Validates format of list of log entries
438 """
438 """
439 log = LogSchemaPermanent()
439 log = LogSchemaPermanent()
440 validator = colander.Length(1)
440 validator = colander.Length(1)
441
441
442
442
443 class ViewRequestStatsSchema(RequestStatsSchema):
443 class ViewRequestStatsSchema(RequestStatsSchema):
444 requests = colander.SchemaNode(colander.Integer(),
444 requests = colander.SchemaNode(colander.Integer(),
445 validator=colander.Range(0),
445 validator=colander.Range(0),
446 missing=0)
446 missing=0)
447
447
448
448
449 class ViewMetricTupleSchema(colander.TupleSchema):
449 class ViewMetricTupleSchema(colander.TupleSchema):
450 """
450 """
451 Validates list of views and their corresponding request stats object ie:
451 Validates list of views and their corresponding request stats object ie:
452 ["dir/module:func",{"custom": 0.0..}]
452 ["dir/module:func",{"custom": 0.0..}]
453 """
453 """
454 view_name = colander.SchemaNode(colander.String(),
454 view_name = colander.SchemaNode(colander.String(),
455 preparer=[shortener_factory(128),
455 preparer=[shortener_factory(128),
456 lambda x: x or 'unknown'],
456 lambda x: x or 'unknown'],
457 missing='unknown')
457 missing='unknown')
458 metrics = ViewRequestStatsSchema()
458 metrics = ViewRequestStatsSchema()
459
459
460
460
461 class ViewMetricListSchema(colander.SequenceSchema):
461 class ViewMetricListSchema(colander.SequenceSchema):
462 """
462 """
463 Validates view breakdown stats objects list
463 Validates view breakdown stats objects list
464 {metrics key of server/time object}
464 {metrics key of server/time object}
465 """
465 """
466 view_tuple = ViewMetricTupleSchema()
466 view_tuple = ViewMetricTupleSchema()
467 validator = colander.Length(1)
467 validator = colander.Length(1)
468
468
469
469
470 class ViewMetricSchema(colander.MappingSchema):
470 class ViewMetricSchema(colander.MappingSchema):
471 """
471 """
472 Validates server/timeinterval object, ie:
472 Validates server/timeinterval object, ie:
473 {server/time object}
473 {server/time object}
474
474
475 """
475 """
476 timestamp = colander.SchemaNode(NonTZDate(),
476 timestamp = colander.SchemaNode(NonTZDate(),
477 validator=limited_date,
477 validator=limited_date,
478 missing=None)
478 missing=None)
479 server = colander.SchemaNode(colander.String(),
479 server = colander.SchemaNode(colander.String(),
480 preparer=[shortener_factory(128),
480 preparer=[shortener_factory(128),
481 lambda x: x or 'unknown'],
481 lambda x: x or 'unknown'],
482 missing='unknown')
482 missing='unknown')
483 metrics = ViewMetricListSchema()
483 metrics = ViewMetricListSchema()
484
484
485
485
486 class GeneralMetricSchema(colander.MappingSchema):
486 class GeneralMetricSchema(colander.MappingSchema):
487 """
487 """
488 Validates universal metric schema
488 Validates universal metric schema
489
489
490 """
490 """
491 namespace = colander.SchemaNode(colander.String(), missing='',
491 namespace = colander.SchemaNode(colander.String(), missing='',
492 preparer=shortener_factory(128))
492 preparer=shortener_factory(128))
493
493
494 server_name = colander.SchemaNode(colander.String(),
494 server_name = colander.SchemaNode(colander.String(),
495 preparer=[shortener_factory(128),
495 preparer=[shortener_factory(128),
496 lambda x: x or 'unknown'],
496 lambda x: x or 'unknown'],
497 missing='unknown')
497 missing='unknown')
498 timestamp = colander.SchemaNode(NonTZDate(), validator=limited_date,
498 timestamp = colander.SchemaNode(NonTZDate(), validator=limited_date,
499 missing=deferred_utcnow)
499 missing=deferred_utcnow)
500 tags = TagSchemaList(missing=colander.required)
500 tags = TagSchemaList(missing=colander.required)
501
501
502
502
503 class GeneralMetricPermanentSchema(GeneralMetricSchema):
503 class GeneralMetricPermanentSchema(GeneralMetricSchema):
504 """
504 """
505 Validates universal metric schema
505 Validates universal metric schema
506
506
507 """
507 """
508 timestamp = colander.SchemaNode(NonTZDate(), missing=deferred_utcnow)
508 timestamp = colander.SchemaNode(NonTZDate(), missing=deferred_utcnow)
509
509
510
510
511 class GeneralMetricsListSchema(colander.SequenceSchema):
511 class GeneralMetricsListSchema(colander.SequenceSchema):
512 metric = GeneralMetricSchema()
512 metric = GeneralMetricSchema()
513 validator = colander.Length(1)
513 validator = colander.Length(1)
514
514
515
515
516 class GeneralMetricsPermanentListSchema(colander.SequenceSchema):
516 class GeneralMetricsPermanentListSchema(colander.SequenceSchema):
517 metric = GeneralMetricPermanentSchema()
517 metric = GeneralMetricPermanentSchema()
518 validator = colander.Length(1)
518 validator = colander.Length(1)
519
519
520
520
521 class MetricsListSchema(colander.SequenceSchema):
521 class MetricsListSchema(colander.SequenceSchema):
522 """
522 """
523 Validates list of metrics objects ie:
523 Validates list of metrics objects ie:
524 [{server/time object}, ] part
524 [{server/time object}, ] part
525
525
526
526
527 """
527 """
528 metric = ViewMetricSchema()
528 metric = ViewMetricSchema()
529 validator = colander.Length(1)
529 validator = colander.Length(1)
530
530
531
531
532 class StringToAppList(object):
532 class StringToAppList(object):
533 """
533 """
534 Returns validated list of application ids from user query and
534 Returns validated list of application ids from user query and
535 set of applications user is allowed to look at
535 set of applications user is allowed to look at
536 transform string to list containing single integer
536 transform string to list containing single integer
537 """
537 """
538
538
539 def serialize(self, node, appstruct):
539 def serialize(self, node, appstruct):
540 if appstruct is null:
540 if appstruct is null:
541 return null
541 return null
542 return appstruct
542 return appstruct
543
543
544 def deserialize(self, node, cstruct):
544 def deserialize(self, node, cstruct):
545 if cstruct is null:
545 if cstruct is null:
546 return null
546 return null
547
547
548 apps = set([int(a) for a in node.bindings['resources']])
548 apps = set([int(a) for a in node.bindings['resources']])
549
549
550 if isinstance(cstruct, str):
550 if isinstance(cstruct, str):
551 cstruct = [cstruct]
551 cstruct = [cstruct]
552
552
553 cstruct = [int(a) for a in cstruct]
553 cstruct = [int(a) for a in cstruct]
554
554
555 valid_apps = list(apps.intersection(set(cstruct)))
555 valid_apps = list(apps.intersection(set(cstruct)))
556 if valid_apps:
556 if valid_apps:
557 return valid_apps
557 return valid_apps
558 return null
558 return null
559
559
560 def cstruct_children(self):
560 def cstruct_children(self):
561 return []
561 return []
562
562
563
563
564 @colander.deferred
564 @colander.deferred
565 def possible_applications_validator(node, kw):
565 def possible_applications_validator(node, kw):
566 possible_apps = [int(a) for a in kw['resources']]
566 possible_apps = [int(a) for a in kw['resources']]
567 return colander.All(colander.ContainsOnly(possible_apps),
567 return colander.All(colander.ContainsOnly(possible_apps),
568 colander.Length(1))
568 colander.Length(1))
569
569
570
570
571 @colander.deferred
571 @colander.deferred
572 def possible_applications(node, kw):
572 def possible_applications(node, kw):
573 return [int(a) for a in kw['resources']]
573 return [int(a) for a in kw['resources']]
574
574
575
575
576 @colander.deferred
576 @colander.deferred
577 def today_start(node, kw):
577 def today_start(node, kw):
578 return datetime.datetime.utcnow().replace(second=0, microsecond=0,
578 return datetime.datetime.utcnow().replace(second=0, microsecond=0,
579 minute=0,
579 minute=0,
580 hour=0)
580 hour=0)
581
581
582
582
583 @colander.deferred
583 @colander.deferred
584 def today_end(node, kw):
584 def today_end(node, kw):
585 return datetime.datetime.utcnow().replace(second=0, microsecond=0,
585 return datetime.datetime.utcnow().replace(second=0, microsecond=0,
586 minute=59, hour=23)
586 minute=59, hour=23)
587
587
588
588
589 @colander.deferred
589 @colander.deferred
590 def old_start(node, kw):
590 def old_start(node, kw):
591 t_delta = datetime.timedelta(days=90)
591 t_delta = datetime.timedelta(days=90)
592 return datetime.datetime.utcnow().replace(second=0, microsecond=0,
592 return datetime.datetime.utcnow().replace(second=0, microsecond=0,
593 minute=0,
593 minute=0,
594 hour=0) - t_delta
594 hour=0) - t_delta
595
595
596
596
597 @colander.deferred
597 @colander.deferred
598 def today_end(node, kw):
598 def today_end(node, kw):
599 return datetime.datetime.utcnow().replace(second=0, microsecond=0,
599 return datetime.datetime.utcnow().replace(second=0, microsecond=0,
600 minute=59, hour=23)
600 minute=59, hour=23)
601
601
602
602
603 class PermissiveDate(colander.DateTime):
603 class PermissiveDate(colander.DateTime):
604 """ Returns null for incorrect date format - also removes tz info"""
604 """ Returns null for incorrect date format - also removes tz info"""
605
605
606 def deserialize(self, node, cstruct):
606 def deserialize(self, node, cstruct):
607 if not cstruct:
607 if not cstruct:
608 return null
608 return null
609
609
610 try:
610 try:
611 result = colander.iso8601.parse_date(
611 result = colander.iso8601.parse_date(
612 cstruct, default_timezone=self.default_tzinfo)
612 cstruct, default_timezone=self.default_tzinfo)
613 except colander.iso8601.ParseError:
613 except colander.iso8601.ParseError:
614 return null
614 return null
615 return result.replace(tzinfo=None)
615 return result.replace(tzinfo=None)
616
616
617
617
618 class LogSearchSchema(colander.MappingSchema):
618 class LogSearchSchema(colander.MappingSchema):
619 def schema_type(self, **kw):
619 def schema_type(self, **kw):
620 return colander.Mapping(unknown='preserve')
620 return colander.Mapping(unknown='preserve')
621
621
622 resource = colander.SchemaNode(StringToAppList(),
622 resource = colander.SchemaNode(StringToAppList(),
623 validator=possible_applications_validator,
623 validator=possible_applications_validator,
624 missing=possible_applications)
624 missing=possible_applications)
625
625
626 message = colander.SchemaNode(colander.Sequence(accept_scalar=True),
626 message = colander.SchemaNode(colander.Sequence(accept_scalar=True),
627 colander.SchemaNode(colander.String()),
627 colander.SchemaNode(colander.String()),
628 missing=None)
628 missing=None)
629 level = colander.SchemaNode(colander.Sequence(accept_scalar=True),
629 level = colander.SchemaNode(colander.Sequence(accept_scalar=True),
630 colander.SchemaNode(colander.String()),
630 colander.SchemaNode(colander.String()),
631 preparer=lowercase_preparer,
631 preparer=lowercase_preparer,
632 missing=None)
632 missing=None)
633 namespace = colander.SchemaNode(colander.Sequence(accept_scalar=True),
633 namespace = colander.SchemaNode(colander.Sequence(accept_scalar=True),
634 colander.SchemaNode(colander.String()),
634 colander.SchemaNode(colander.String()),
635 preparer=lowercase_preparer,
635 preparer=lowercase_preparer,
636 missing=None)
636 missing=None)
637 request_id = colander.SchemaNode(colander.Sequence(accept_scalar=True),
637 request_id = colander.SchemaNode(colander.Sequence(accept_scalar=True),
638 colander.SchemaNode(colander.String()),
638 colander.SchemaNode(colander.String()),
639 preparer=lowercase_preparer,
639 preparer=lowercase_preparer,
640 missing=None)
640 missing=None)
641 start_date = colander.SchemaNode(PermissiveDate(),
641 start_date = colander.SchemaNode(PermissiveDate(),
642 missing=None)
642 missing=None)
643 end_date = colander.SchemaNode(PermissiveDate(),
643 end_date = colander.SchemaNode(PermissiveDate(),
644 missing=None)
644 missing=None)
645 page = colander.SchemaNode(colander.Integer(),
645 page = colander.SchemaNode(colander.Integer(),
646 validator=colander.Range(min=1),
646 validator=colander.Range(min=1),
647 missing=1)
647 missing=1)
648
648
649
649
650 class ReportSearchSchema(colander.MappingSchema):
650 class ReportSearchSchema(colander.MappingSchema):
651 def schema_type(self, **kw):
651 def schema_type(self, **kw):
652 return colander.Mapping(unknown='preserve')
652 return colander.Mapping(unknown='preserve')
653
653
654 resource = colander.SchemaNode(StringToAppList(),
654 resource = colander.SchemaNode(StringToAppList(),
655 validator=possible_applications_validator,
655 validator=possible_applications_validator,
656 missing=possible_applications)
656 missing=possible_applications)
657 request_id = colander.SchemaNode(colander.Sequence(accept_scalar=True),
657 request_id = colander.SchemaNode(colander.Sequence(accept_scalar=True),
658 colander.SchemaNode(colander.String()),
658 colander.SchemaNode(colander.String()),
659 missing=None)
659 missing=None)
660 start_date = colander.SchemaNode(PermissiveDate(),
660 start_date = colander.SchemaNode(PermissiveDate(),
661 missing=None)
661 missing=None)
662 end_date = colander.SchemaNode(PermissiveDate(),
662 end_date = colander.SchemaNode(PermissiveDate(),
663 missing=None)
663 missing=None)
664 page = colander.SchemaNode(colander.Integer(),
664 page = colander.SchemaNode(colander.Integer(),
665 validator=colander.Range(min=1),
665 validator=colander.Range(min=1),
666 missing=1)
666 missing=1)
667
667
668 min_occurences = colander.SchemaNode(
668 min_occurences = colander.SchemaNode(
669 colander.Sequence(accept_scalar=True),
669 colander.Sequence(accept_scalar=True),
670 colander.SchemaNode(colander.Integer()),
670 colander.SchemaNode(colander.Integer()),
671 missing=None)
671 missing=None)
672
672
673 http_status = colander.SchemaNode(colander.Sequence(accept_scalar=True),
673 http_status = colander.SchemaNode(colander.Sequence(accept_scalar=True),
674 colander.SchemaNode(colander.Integer()),
674 colander.SchemaNode(colander.Integer()),
675 missing=None)
675 missing=None)
676 priority = colander.SchemaNode(colander.Sequence(accept_scalar=True),
676 priority = colander.SchemaNode(colander.Sequence(accept_scalar=True),
677 colander.SchemaNode(colander.Integer()),
677 colander.SchemaNode(colander.Integer()),
678 missing=None)
678 missing=None)
679 error = colander.SchemaNode(colander.Sequence(accept_scalar=True),
679 error = colander.SchemaNode(colander.Sequence(accept_scalar=True),
680 colander.SchemaNode(colander.String()),
680 colander.SchemaNode(colander.String()),
681 missing=None)
681 missing=None)
682 url_path = colander.SchemaNode(colander.Sequence(accept_scalar=True),
682 url_path = colander.SchemaNode(colander.Sequence(accept_scalar=True),
683 colander.SchemaNode(colander.String()),
683 colander.SchemaNode(colander.String()),
684 missing=None)
684 missing=None)
685 url_domain = colander.SchemaNode(colander.Sequence(accept_scalar=True),
685 url_domain = colander.SchemaNode(colander.Sequence(accept_scalar=True),
686 colander.SchemaNode(colander.String()),
686 colander.SchemaNode(colander.String()),
687 missing=None)
687 missing=None)
688 report_status = colander.SchemaNode(colander.Sequence(accept_scalar=True),
688 report_status = colander.SchemaNode(colander.Sequence(accept_scalar=True),
689 colander.SchemaNode(colander.String()),
689 colander.SchemaNode(colander.String()),
690 missing=None)
690 missing=None)
691 min_duration = colander.SchemaNode(colander.Sequence(accept_scalar=True),
691 min_duration = colander.SchemaNode(colander.Sequence(accept_scalar=True),
692 colander.SchemaNode(colander.Float()),
692 colander.SchemaNode(colander.Float()),
693 missing=None)
693 missing=None)
694 max_duration = colander.SchemaNode(colander.Sequence(accept_scalar=True),
694 max_duration = colander.SchemaNode(colander.Sequence(accept_scalar=True),
695 colander.SchemaNode(colander.Float()),
695 colander.SchemaNode(colander.Float()),
696 missing=None)
696 missing=None)
697
697
698
698
699 class TagSchema(colander.MappingSchema):
699 class TagSchema(colander.MappingSchema):
700 """
700 """
701 Used in log search
701 Used in log search
702 """
702 """
703 name = colander.SchemaNode(colander.String(),
703 name = colander.SchemaNode(colander.String(),
704 validator=colander.Length(1, 32))
704 validator=colander.Length(1, 32))
705 value = colander.SchemaNode(colander.Sequence(accept_scalar=True),
705 value = colander.SchemaNode(colander.Sequence(accept_scalar=True),
706 colander.SchemaNode(colander.String(),
706 colander.SchemaNode(colander.String(),
707 validator=colander.Length(
707 validator=colander.Length(
708 1, 128)),
708 1, 128)),
709 missing=None)
709 missing=None)
710 op = colander.SchemaNode(colander.String(),
710 op = colander.SchemaNode(colander.String(),
711 validator=colander.Length(1, 128),
711 validator=colander.Length(1, 128),
712 missing=None)
712 missing=None)
713
713
714
714
715 class TagListSchema(colander.SequenceSchema):
715 class TagListSchema(colander.SequenceSchema):
716 tag = TagSchema()
716 tag = TagSchema()
717
717
718
718
719 class RuleFieldType(object):
719 class RuleFieldType(object):
720 """ Validator which succeeds if the value passed to it is one of
720 """ Validator which succeeds if the value passed to it is one of
721 a fixed set of values """
721 a fixed set of values """
722
722
723 def __init__(self, cast_to):
723 def __init__(self, cast_to):
724 self.cast_to = cast_to
724 self.cast_to = cast_to
725
725
726 def __call__(self, node, value):
726 def __call__(self, node, value):
727 try:
727 try:
728 if self.cast_to == 'int':
728 if self.cast_to == 'int':
729 int(value)
729 int(value)
730 elif self.cast_to == 'float':
730 elif self.cast_to == 'float':
731 float(value)
731 float(value)
732 elif self.cast_to == 'unicode':
732 elif self.cast_to == 'unicode':
733 str(value)
733 str(value)
734 except:
734 except:
735 raise colander.Invalid(node,
735 raise colander.Invalid(node,
736 "Can't cast {} to {}".format(
736 "Can't cast {} to {}".format(
737 value, self.cast_to))
737 value, self.cast_to))
738
738
739
739
740 def build_rule_schema(ruleset, check_matrix):
740 def build_rule_schema(ruleset, check_matrix):
741 """
741 """
742 Accepts ruleset and a map of fields/possible operations and builds
742 Accepts ruleset and a map of fields/possible operations and builds
743 validation class
743 validation class
744 """
744 """
745
745
746 schema = colander.SchemaNode(colander.Mapping())
746 schema = colander.SchemaNode(colander.Mapping())
747 schema.add(colander.SchemaNode(colander.String(), name='field'))
747 schema.add(colander.SchemaNode(colander.String(), name='field'))
748
748
749 if ruleset['field'] in ['__AND__', '__OR__', '__NOT__']:
749 if ruleset['field'] in ['__AND__', '__OR__', '__NOT__']:
750 subrules = colander.SchemaNode(colander.Tuple(), name='rules')
750 subrules = colander.SchemaNode(colander.Tuple(), name='rules')
751 for rule in ruleset['rules']:
751 for rule in ruleset['rules']:
752 subrules.add(build_rule_schema(rule, check_matrix))
752 subrules.add(build_rule_schema(rule, check_matrix))
753 schema.add(subrules)
753 schema.add(subrules)
754 else:
754 else:
755 op_choices = check_matrix[ruleset['field']]['ops']
755 op_choices = check_matrix[ruleset['field']]['ops']
756 cast_to = check_matrix[ruleset['field']]['type']
756 cast_to = check_matrix[ruleset['field']]['type']
757 schema.add(colander.SchemaNode(colander.String(),
757 schema.add(colander.SchemaNode(colander.String(),
758 validator=colander.OneOf(op_choices),
758 validator=colander.OneOf(op_choices),
759 name='op'))
759 name='op'))
760
760
761 schema.add(colander.SchemaNode(colander.String(),
761 schema.add(colander.SchemaNode(colander.String(),
762 name='value',
762 name='value',
763 validator=RuleFieldType(cast_to)))
763 validator=RuleFieldType(cast_to)))
764 return schema
764 return schema
765
765
766
766
767 class ConfigTypeSchema(colander.MappingSchema):
767 class ConfigTypeSchema(colander.MappingSchema):
768 type = colander.SchemaNode(colander.String(), missing=None)
768 type = colander.SchemaNode(colander.String(), missing=None)
769 config = colander.SchemaNode(UnknownType(), missing=None)
769 config = colander.SchemaNode(UnknownType(), missing=None)
770
770
771
771
772 class MappingListSchema(colander.SequenceSchema):
772 class MappingListSchema(colander.SequenceSchema):
773 config = colander.SchemaNode(UnknownType())
773 config = colander.SchemaNode(UnknownType())
General Comments 0
You need to be logged in to leave comments. Login now