blob: 2afbc85c3a4ac15fbc12a2c39fc44037a49944a4 [file] [log] [blame]
tierno7edb6752016-03-21 17:37:52 +01001# -*- coding: utf-8 -*-
2
3##
tierno92021022018-09-12 16:29:23 +02004# Copyright 2015 Telefonica Investigacion y Desarrollo, S.A.U.
tierno7edb6752016-03-21 17:37:52 +01005# This file is part of openmano
6# All Rights Reserved.
7#
8# Licensed under the Apache License, Version 2.0 (the "License"); you may
9# not use this file except in compliance with the License. You may obtain
10# a copy of the License at
11#
12# http://www.apache.org/licenses/LICENSE-2.0
13#
14# Unless required by applicable law or agreed to in writing, software
15# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
16# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
17# License for the specific language governing permissions and limitations
18# under the License.
19#
20# For those usages not covered by the Apache License, Version 2.0 please
21# contact with: nfvlabs@tid.es
22##
23
24'''
tierno66aa0372016-07-06 17:31:12 +020025utils is a module that implements functions that are used by all openmano modules,
tierno7edb6752016-03-21 17:37:52 +010026dealing with aspects such as reading/writing files, formatting inputs/outputs for quick translation
27from dictionaries to appropriate database dictionaries, etc.
28'''
29__author__="Alfonso Tierno, Gerardo Garcia"
30__date__ ="$08-sep-2014 12:21:22$"
31
32import datetime
Anderson Bravalheri0446cd52018-08-17 15:26:19 +010033import time
tiernob8569aa2018-08-24 11:34:54 +020034import warnings
Anderson Bravalheri0446cd52018-08-17 15:26:19 +010035from functools import reduce
36from itertools import tee
37
38from six.moves import filter, filterfalse
39
40from jsonschema import exceptions as js_e
41from jsonschema import validate as js_v
42
tierno7edb6752016-03-21 17:37:52 +010043#from bs4 import BeautifulSoup
44
45def read_file(file_to_read):
46 """Reads a file specified by 'file_to_read' and returns (True,<its content as a string>) in case of success or (False, <error message>) in case of failure"""
47 try:
48 f = open(file_to_read, 'r')
49 read_data = f.read()
50 f.close()
venkatamahesh6ecca182017-01-27 23:04:40 +053051 except Exception as e:
tierno7edb6752016-03-21 17:37:52 +010052 return (False, str(e))
Anderson Bravalheri0446cd52018-08-17 15:26:19 +010053
tierno7edb6752016-03-21 17:37:52 +010054 return (True, read_data)
55
56def write_file(file_to_write, text):
57 """Write a file specified by 'file_to_write' and returns (True,NOne) in case of success or (False, <error message>) in case of failure"""
58 try:
59 f = open(file_to_write, 'w')
60 f.write(text)
61 f.close()
venkatamahesh6ecca182017-01-27 23:04:40 +053062 except Exception as e:
tierno7edb6752016-03-21 17:37:52 +010063 return (False, str(e))
Anderson Bravalheri0446cd52018-08-17 15:26:19 +010064
tierno7edb6752016-03-21 17:37:52 +010065 return (True, None)
66
67def format_in(http_response, schema):
68 try:
69 client_data = http_response.json()
70 js_v(client_data, schema)
71 #print "Input data: ", str(client_data)
72 return True, client_data
venkatamahesh6ecca182017-01-27 23:04:40 +053073 except js_e.ValidationError as exc:
tierno7edb6752016-03-21 17:37:52 +010074 print "validate_in error, jsonschema exception ", exc.message, "at", exc.path
75 return False, ("validate_in error, jsonschema exception ", exc.message, "at", exc.path)
76
77def remove_extra_items(data, schema):
78 deleted=[]
79 if type(data) is tuple or type(data) is list:
80 for d in data:
81 a= remove_extra_items(d, schema['items'])
82 if a is not None: deleted.append(a)
83 elif type(data) is dict:
84 #TODO deal with patternProperties
85 if 'properties' not in schema:
86 return None
87 for k in data.keys():
88 if k not in schema['properties'].keys():
89 del data[k]
90 deleted.append(k)
91 else:
92 a = remove_extra_items(data[k], schema['properties'][k])
93 if a is not None: deleted.append({k:a})
94 if len(deleted) == 0: return None
95 elif len(deleted) == 1: return deleted[0]
96 else: return deleted
97
98#def format_html2text(http_content):
99# soup=BeautifulSoup(http_content)
100# text = soup.p.get_text() + " " + soup.pre.get_text()
101# return text
102
103
Anderson Bravalheri0446cd52018-08-17 15:26:19 +0100104def delete_nulls(var):
105 if type(var) is dict:
106 for k in var.keys():
107 if var[k] is None: del var[k]
108 elif type(var[k]) is dict or type(var[k]) is list or type(var[k]) is tuple:
109 if delete_nulls(var[k]): del var[k]
110 if len(var) == 0: return True
111 elif type(var) is list or type(var) is tuple:
112 for k in var:
113 if type(k) is dict: delete_nulls(k)
114 if len(var) == 0: return True
115 return False
116
117
tierno7edb6752016-03-21 17:37:52 +0100118def convert_bandwidth(data, reverse=False):
Anderson Bravalheri0446cd52018-08-17 15:26:19 +0100119 '''Check the field bandwidth recursivelly and when found, it removes units and convert to number
tierno7edb6752016-03-21 17:37:52 +0100120 It assumes that bandwidth is well formed
121 Attributes:
122 'data': dictionary bottle.FormsDict variable to be checked. None or empty is consideted valid
123 'reverse': by default convert form str to int (Mbps), if True it convert from number to units
124 Return:
125 None
126 '''
127 if type(data) is dict:
128 for k in data.keys():
129 if type(data[k]) is dict or type(data[k]) is tuple or type(data[k]) is list:
130 convert_bandwidth(data[k], reverse)
131 if "bandwidth" in data:
132 try:
133 value=str(data["bandwidth"])
134 if not reverse:
135 pos = value.find("bps")
136 if pos>0:
137 if value[pos-1]=="G": data["bandwidth"] = int(data["bandwidth"][:pos-1]) * 1000
138 elif value[pos-1]=="k": data["bandwidth"]= int(data["bandwidth"][:pos-1]) / 1000
139 else: data["bandwidth"]= int(data["bandwidth"][:pos-1])
140 else:
141 value = int(data["bandwidth"])
142 if value % 1000 == 0: data["bandwidth"]=str(value/1000) + " Gbps"
143 else: data["bandwidth"]=str(value) + " Mbps"
144 except:
145 print "convert_bandwidth exception for type", type(data["bandwidth"]), " data", data["bandwidth"]
146 return
147 if type(data) is tuple or type(data) is list:
148 for k in data:
149 if type(k) is dict or type(k) is tuple or type(k) is list:
150 convert_bandwidth(k, reverse)
151
Anderson Bravalheri0446cd52018-08-17 15:26:19 +0100152def convert_float_timestamp2str(var):
153 '''Converts timestamps (created_at, modified_at fields) represented as float
154 to a string with the format '%Y-%m-%dT%H:%i:%s'
155 It enters recursively in the dict var finding this kind of variables
156 '''
157 if type(var) is dict:
158 for k,v in var.items():
159 if type(v) is float and k in ("created_at", "modified_at"):
160 var[k] = time.strftime("%Y-%m-%dT%H:%M:%S", time.localtime(v) )
161 elif type(v) is dict or type(v) is list or type(v) is tuple:
162 convert_float_timestamp2str(v)
163 if len(var) == 0: return True
164 elif type(var) is list or type(var) is tuple:
165 for v in var:
166 convert_float_timestamp2str(v)
tierno7edb6752016-03-21 17:37:52 +0100167
168def convert_datetime2str(var):
169 '''Converts a datetime variable to a string with the format '%Y-%m-%dT%H:%i:%s'
170 It enters recursively in the dict var finding this kind of variables
171 '''
172 if type(var) is dict:
173 for k,v in var.items():
174 if type(v) is datetime.datetime:
175 var[k]= v.strftime('%Y-%m-%dT%H:%M:%S')
Anderson Bravalheri0446cd52018-08-17 15:26:19 +0100176 elif type(v) is dict or type(v) is list or type(v) is tuple:
tierno7edb6752016-03-21 17:37:52 +0100177 convert_datetime2str(v)
178 if len(var) == 0: return True
179 elif type(var) is list or type(var) is tuple:
180 for v in var:
181 convert_datetime2str(v)
182
183def convert_str2boolean(data, items):
Anderson Bravalheri0446cd52018-08-17 15:26:19 +0100184 '''Check recursively the content of data, and if there is an key contained in items, convert value from string to boolean
tierno7edb6752016-03-21 17:37:52 +0100185 Done recursively
186 Attributes:
187 'data': dictionary variable to be checked. None or empty is considered valid
188 'items': tuple of keys to convert
189 Return:
190 None
191 '''
192 if type(data) is dict:
193 for k in data.keys():
194 if type(data[k]) is dict or type(data[k]) is tuple or type(data[k]) is list:
195 convert_str2boolean(data[k], items)
196 if k in items:
197 if type(data[k]) is str:
198 if data[k]=="false" or data[k]=="False": data[k]=False
199 elif data[k]=="true" or data[k]=="True": data[k]=True
200 if type(data) is tuple or type(data) is list:
201 for k in data:
202 if type(k) is dict or type(k) is tuple or type(k) is list:
203 convert_str2boolean(k, items)
204
205def check_valid_uuid(uuid):
206 id_schema = {"type" : "string", "pattern": "^[a-fA-F0-9]{8}(-[a-fA-F0-9]{4}){3}-[a-fA-F0-9]{12}$"}
tiernoae4a8d12016-07-08 12:30:39 +0200207 id_schema2 = {"type" : "string", "pattern": "^[a-fA-F0-9]{32}$"}
tierno7edb6752016-03-21 17:37:52 +0100208 try:
209 js_v(uuid, id_schema)
210 return True
211 except js_e.ValidationError:
tiernoae4a8d12016-07-08 12:30:39 +0200212 try:
213 js_v(uuid, id_schema2)
214 return True
215 except js_e.ValidationError:
216 return False
217 return False
tierno7f426e92018-06-28 15:21:32 +0200218
219
220def expand_brackets(text):
221 """
222 Change a text with TEXT[ABC..] into a list with [TEXTA, TEXTB, TEXC, ...
223 if no bracket is used it just return the a list with the single text
224 It uses recursivity to allow several [] in the text
225 :param text:
226 :return:
227 """
228 start = text.find("[")
229 end = text.find("]")
230 if start < 0 or end < 0:
231 return [text]
232 text_list = []
233 for char in text[start+1:end]:
234 text_list += expand_brackets(text[:start] + char + text[end+1:])
235 return text_list
tiernob8569aa2018-08-24 11:34:54 +0200236
237def deprecated(message):
238 def deprecated_decorator(func):
239 def deprecated_func(*args, **kwargs):
240 warnings.warn("{} is a deprecated function. {}".format(func.__name__, message),
241 category=DeprecationWarning,
242 stacklevel=2)
243 warnings.simplefilter('default', DeprecationWarning)
244 return func(*args, **kwargs)
245 return deprecated_func
Anderson Bravalheri0446cd52018-08-17 15:26:19 +0100246 return deprecated_decorator
247
248
249def truncate(text, max_length=1024):
250 """Limit huge texts in number of characters"""
251 text = str(text)
252 if text and len(text) >= max_length:
253 return text[:max_length//2-3] + " ... " + text[-max_length//2+3:]
254 return text
255
256
257def merge_dicts(*dicts, **kwargs):
258 """Creates a new dict merging N others and keyword arguments.
259 Right-most dicts take precedence.
260 Keyword args take precedence.
261 """
262 return reduce(
263 lambda acc, x: acc.update(x) or acc,
264 list(dicts) + [kwargs], {})
265
266
267def remove_none_items(adict):
268 """Return a similar dict without keys associated to None values"""
269 return {k: v for k, v in adict.items() if v is not None}
270
271
272def filter_dict_keys(adict, allow):
273 """Return a similar dict, but just containing the explicitly allowed keys
274
275 Arguments:
276 adict (dict): Simple python dict data struct
277 allow (list): Explicits allowed keys
278 """
279 return {k: v for k, v in adict.items() if k in allow}
280
281
282def filter_out_dict_keys(adict, deny):
283 """Return a similar dict, but not containing the explicitly denied keys
284
285 Arguments:
286 adict (dict): Simple python dict data struct
287 deny (list): Explicits denied keys
288 """
289 return {k: v for k, v in adict.items() if k not in deny}
290
291
292def expand_joined_fields(record):
293 """Given a db query result, explode the fields that contains `.` (join
294 operations).
295
296 Example
297 >> expand_joined_fiels({'wim.id': 2})
298 # {'wim': {'id': 2}}
299 """
300 result = {}
301 for field, value in record.items():
302 keys = field.split('.')
303 target = result
304 target = reduce(lambda target, key: target.setdefault(key, {}),
305 keys[:-1], result)
306 target[keys[-1]] = value
307
308 return result
309
310
311def ensure(condition, exception):
312 """Raise an exception if condition is not met"""
313 if not condition:
314 raise exception
315
316
317def partition(predicate, iterable):
318 """Create two derived iterators from a single one
319 The first iterator created will loop thought the values where the function
320 predicate is True, the second one will iterate over the values where it is
321 false.
322 """
323 iterable1, iterable2 = tee(iterable)
324 return filter(predicate, iterable2), filterfalse(predicate, iterable1)
325
326
327def pipe(*functions):
328 """Compose functions of one argument in the opposite order,
329 So pipe(f, g)(x) = g(f(x))
330 """
331 return lambda x: reduce(lambda acc, f: f(acc), functions, x)
332
333
334def compose(*functions):
335 """Compose functions of one argument,
336 So compose(f, g)(x) = f(g(x))
337 """
338 return lambda x: reduce(lambda acc, f: f(acc), functions[::-1], x)
339
340
341def safe_get(target, key_path, default=None):
342 """Given a path of keys (eg.: "key1.key2.key3"), return a nested value in
343 a nested dict if present, or the default value
344 """
345 keys = key_path.split('.')
346 target = reduce(lambda acc, key: acc.get(key) or {}, keys[:-1], target)
347 return target.get(keys[-1], default)