blob: 1e3a8ee74a8b283b7f847fa6fa7b4e5ea77f1a0a [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 """
tierno4070e442019-01-23 10:19:23 +0000228 if text is None:
229 return (None, )
tierno7f426e92018-06-28 15:21:32 +0200230 start = text.find("[")
231 end = text.find("]")
232 if start < 0 or end < 0:
233 return [text]
234 text_list = []
235 for char in text[start+1:end]:
236 text_list += expand_brackets(text[:start] + char + text[end+1:])
237 return text_list
tiernob8569aa2018-08-24 11:34:54 +0200238
239def deprecated(message):
240 def deprecated_decorator(func):
241 def deprecated_func(*args, **kwargs):
242 warnings.warn("{} is a deprecated function. {}".format(func.__name__, message),
243 category=DeprecationWarning,
244 stacklevel=2)
245 warnings.simplefilter('default', DeprecationWarning)
246 return func(*args, **kwargs)
247 return deprecated_func
Anderson Bravalheri0446cd52018-08-17 15:26:19 +0100248 return deprecated_decorator
249
250
251def truncate(text, max_length=1024):
252 """Limit huge texts in number of characters"""
253 text = str(text)
254 if text and len(text) >= max_length:
255 return text[:max_length//2-3] + " ... " + text[-max_length//2+3:]
256 return text
257
258
259def merge_dicts(*dicts, **kwargs):
260 """Creates a new dict merging N others and keyword arguments.
261 Right-most dicts take precedence.
262 Keyword args take precedence.
263 """
264 return reduce(
265 lambda acc, x: acc.update(x) or acc,
266 list(dicts) + [kwargs], {})
267
268
269def remove_none_items(adict):
270 """Return a similar dict without keys associated to None values"""
271 return {k: v for k, v in adict.items() if v is not None}
272
273
274def filter_dict_keys(adict, allow):
275 """Return a similar dict, but just containing the explicitly allowed keys
276
277 Arguments:
278 adict (dict): Simple python dict data struct
279 allow (list): Explicits allowed keys
280 """
281 return {k: v for k, v in adict.items() if k in allow}
282
283
284def filter_out_dict_keys(adict, deny):
285 """Return a similar dict, but not containing the explicitly denied keys
286
287 Arguments:
288 adict (dict): Simple python dict data struct
289 deny (list): Explicits denied keys
290 """
291 return {k: v for k, v in adict.items() if k not in deny}
292
293
294def expand_joined_fields(record):
295 """Given a db query result, explode the fields that contains `.` (join
296 operations).
297
298 Example
299 >> expand_joined_fiels({'wim.id': 2})
300 # {'wim': {'id': 2}}
301 """
302 result = {}
303 for field, value in record.items():
304 keys = field.split('.')
305 target = result
306 target = reduce(lambda target, key: target.setdefault(key, {}),
307 keys[:-1], result)
308 target[keys[-1]] = value
309
310 return result
311
312
313def ensure(condition, exception):
314 """Raise an exception if condition is not met"""
315 if not condition:
316 raise exception
317
318
319def partition(predicate, iterable):
320 """Create two derived iterators from a single one
321 The first iterator created will loop thought the values where the function
322 predicate is True, the second one will iterate over the values where it is
323 false.
324 """
325 iterable1, iterable2 = tee(iterable)
326 return filter(predicate, iterable2), filterfalse(predicate, iterable1)
327
328
329def pipe(*functions):
330 """Compose functions of one argument in the opposite order,
331 So pipe(f, g)(x) = g(f(x))
332 """
333 return lambda x: reduce(lambda acc, f: f(acc), functions, x)
334
335
336def compose(*functions):
337 """Compose functions of one argument,
338 So compose(f, g)(x) = f(g(x))
339 """
340 return lambda x: reduce(lambda acc, f: f(acc), functions[::-1], x)
341
342
343def safe_get(target, key_path, default=None):
344 """Given a path of keys (eg.: "key1.key2.key3"), return a nested value in
345 a nested dict if present, or the default value
346 """
347 keys = key_path.split('.')
348 target = reduce(lambda acc, key: acc.get(key) or {}, keys[:-1], target)
349 return target.get(keys[-1], default)