File: params.py

package info (click to toggle)
graphite-web 1.1.10-9
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 7,864 kB
  • sloc: javascript: 86,828; python: 12,228; sh: 91; makefile: 54
file content (315 lines) | stat: -rw-r--r-- 10,118 bytes parent folder | download | duplicates (3)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
import six

from graphite.errors import InputParameterError
from graphite.render.attime import parseTimeOffset
from graphite.logger import log
from graphite.functions.aggfuncs import aggFuncs, aggFuncAliases
from graphite.render.datalib import TimeSeries


class ParamTypes(object):
    pass


class ParamType(object):
    options = []

    def __init__(self, name, validator=None):
        self.name = name
        self.validator = validator

    @classmethod
    def register(cls, name, *args):
        setattr(ParamTypes, name, cls(name, *args))

    def isValid(self, value):
        if self.validator is None:
            # if there's no validator for the type we assume True
            return value

        return self.validator(value)


def validateBoolean(value):
    if isinstance(value, six.string_types):
        if value.lower() == 'true':
            return True
        if value.lower() == 'false':
            return False
        raise ValueError('Invalid boolean value: {value}'.format(value=repr(value)))

    if type(value) in [int, float]:
        if value == 0:
            return False
        if value == 1:
            return True
        raise ValueError('Invalid boolean value: {value}'.format(value=repr(value)))

    return bool(value)


def validateFloat(value):
    return float(value)


def validateInteger(value):
    # prevent that float values get converted to int, because an
    # error is better than silently falsifying the result
    if type(value) is float:
        raise ValueError('Not a valid integer value: {value}'.format(value=repr(value)))

    return int(value)


def validateIntOrInf(value):
    try:
        return validateInteger(value)
    except (TypeError, ValueError):
        pass

    try:
        inf = float('inf')
        if float(value) == inf:
            return inf
    except (TypeError, ValueError, OverflowError):
        pass

    raise ValueError('Not a valid integer nor float value: {value}'.format(value=repr(value)))


def validateInterval(value):
    try:
        parseTimeOffset(value)
    except (IndexError, KeyError, TypeError, ValueError) as e:
        raise ValueError('Invalid interval value: {value}: {e}'.format(value=repr(value), e=str(e)))
    return value


def validateSeriesList(value):
    if not isinstance(value, list):
        raise ValueError('Invalid value type, it is not a list: {value}'.format(value=repr(value)))

    for series in value:
        if not isinstance(series, TimeSeries):
            raise ValueError('Invalid type "{type}", should be TimeSeries'.format(type=type(series)))

    return value


def validateSeriesLists(value):
    if not isinstance(value, list):
        raise ValueError('Invalid value type, it is not a list: {value}'.format(value=repr(value)))

    for entry in value:
        validateSeriesList(entry)

    return value


ParamType.register('boolean', validateBoolean)
ParamType.register('date')
ParamType.register('float', validateFloat)
ParamType.register('integer', validateInteger)
ParamType.register('interval', validateInterval)
ParamType.register('intOrInterval')
ParamType.register('intOrInf', validateIntOrInf)
ParamType.register('node', validateInteger)
ParamType.register('nodeOrTag')
ParamType.register('series')
ParamType.register('seriesList', validateSeriesList)
ParamType.register('seriesLists', validateSeriesLists)
ParamType.register('string')
ParamType.register('tag')

# special type that accepts everything
ParamType.register('any')


class ParamTypeAggFunc(ParamType):

    def __init__(self, name, validator=None):
        if validator is None:
            validator = self.validateAggFuncs

        super(ParamTypeAggFunc, self).__init__(name=name, validator=validator)
        self.options = self.getValidAggFuncs()

    @classmethod
    def getValidAggFuncs(cls):
        return list(aggFuncs.keys()) + list(aggFuncAliases.keys())

    @classmethod
    def getDeprecatedAggFuncs(cls):
        return [name + 'Series' for name in cls.getValidAggFuncs()]

    @classmethod
    def getAllValidAggFuncs(cls):
        return cls.getValidAggFuncs() + cls.getDeprecatedAggFuncs()

    def validateAggFuncs(self, value):
        if value in self.getValidAggFuncs():
            return value

        if value in self.getDeprecatedAggFuncs():
            log.warning('Deprecated aggregation function: {value}'.format(value=repr(value)))
            return value

        raise ValueError('Invalid aggregation function: {value}'.format(value=repr(value)))


ParamTypeAggFunc.register('aggFunc')


class ParamTypeAggOrSeriesFunc(ParamTypeAggFunc):
    options = []

    def __init__(self, name, validator=None):
        if validator is None:
            validator = self.validateAggOrSeriesFuncs
        super(ParamTypeAggOrSeriesFunc, self).__init__(name=name, validator=validator)

    def setSeriesFuncs(self, funcs):
        # check for each of the series functions whether they have an 'aggregator'
        # property being set to 'True'. If so we consider them valid aggregators.
        for name, func in funcs.items():
            if getattr(func, 'aggregator', False) is not True:
                continue

            self.options.append(name)

    def validateAggOrSeriesFuncs(self, value):
        try:
            return self.validateAggFuncs(value)
        except ValueError:
            pass

        if value in self.options:
            return value

        return False


ParamTypeAggOrSeriesFunc.register('aggOrSeriesFunc')


class Param(object):
    __slots__ = ('name', 'type', 'required', 'default', 'multiple', '_options', 'suggestions')

    def __init__(self, name, paramtype, required=False, default=None, multiple=False, options=None, suggestions=None):
        self.name = name
        if not isinstance(paramtype, ParamType):
            raise Exception('Invalid type %s for parameter %s' % (paramtype, name))
        self.type = paramtype
        self.required = bool(required)
        self.default = default
        self.multiple = bool(multiple)
        if options is None:
            options = []
        self._options = options
        self.suggestions = suggestions

    @property
    def options(self):
        options = list(set(getattr(self, '_options', []) + getattr(self.type, 'options', [])))
        options.sort(key=str)
        return options

    def toJSON(self):
        jsonVal = {
            'name': self.name,
            'type': self.type.name,
        }
        if self.required:
            jsonVal['required'] = True
        if self.default is not None:
            jsonVal['default'] = self.default
        if self.multiple:
            jsonVal['multiple'] = True
        if self.options:
            jsonVal['options'] = self.options
        if self.suggestions:
            jsonVal['suggestions'] = self.suggestions
        return jsonVal

    def validateValue(self, value, func):
        # if value isn't specified and there's a default then the default will be used,
        # we don't need to validate the default value because we trust that it is valid
        if value is None and self.default is not None:
            return value

        # None is ok for optional params
        if not self.required and value is None:
            return value

        # parameter is restricted to a defined set of values, but value is not in it
        if self.options and value not in self.options:
            raise InputParameterError(
                'Invalid option specified for function "{func}" parameter "{param}": {value}'.format(
                    func=func, param=self.name, value=repr(value)))

        try:
            return self.type.isValid(value)
        except Exception:
            raise InputParameterError(
                'Invalid "{type}" value specified for function "{func}" parameter "{param}": {value}'.format(
                    type=self.type.name, func=func, param=self.name, value=repr(value)))


def validateParams(func, params, args, kwargs):
    valid_args = []
    valid_kwargs = {}

    # total number of args + kwargs might be larger than number of params if
    # the last param allows to be specified multiple times
    if len(args) + len(kwargs) > len(params):
        if not params[-1].multiple:
            raise InputParameterError(
                'Too many parameters specified for function "{func}"'.format(func=func))

        # if args has more values than params and the last param allows multiple values,
        # then we're going to validate all paramas from the args value list
        args_params = params
        kwargs_params = []
    else:
        # take the first len(args) params and use them to validate the args values,
        # use the remaining params to validate the kwargs values
        args_params = params[:len(args)]
        kwargs_params = params[len(args):]

    # validate the args
    for (i, arg) in enumerate(args):
        if i >= len(args_params):
            # last parameter must be allowing multiple,
            # so we're using it to validate this arg
            param = args_params[-1]
        else:
            param = args_params[i]

        valid_args.append(param.validateValue(arg, func))

    # validate the kwargs
    for param in kwargs_params:
        value = kwargs.get(param.name, None)
        if value is None:
            if param.required:
                raise InputParameterError(
                    'Missing required parameter "{param}" for function "{func}"'
                    .format(param=param.name, func=func))
            continue

        valid_kwargs[param.name] = param.validateValue(value, func)

    if len(kwargs) > len(valid_kwargs):
        unexpected_keys = []
        for name in kwargs.keys():
            if name not in valid_kwargs:
                unexpected_keys.append(name)
        raise InputParameterError(
            'Unexpected key word arguments: {keys}'.format(
                keys=', '.join(
                    key
                    for key in kwargs.keys()
                    if key not in valid_kwargs.keys()
                )))

    return (valid_args, valid_kwargs)