1#!/usr/bin/env python3
2
3"""Misc utility functions."""
4
5import os
6import logging
7from datetime import timedelta
8import math
9import sys
10
11# do not import any other chart modules here because this module is parsed very early
12# in startup from settings.py
13
14
15def ensure_dir_exists(dirname, umask=None):
16 """Create `dirname` if it doesn't already exist."""
17 if dirname is None:
18 raise ValueError('ensure_dir_exists() called with no path')
19
20 if dirname.is_file():
21 raise ValueError('{path} exists and is a file'.format(path=dirname))
22
23 if not dirname.is_dir():
24 logging.info('Creating directory {name}'.format(name=dirname))
25 if umask is not None:
26 old_umask = os.umask(umask)
27
28 dirname.mkdir(parents=True)
29
30 if umask is not None:
31 os.umask(old_umask)
32
33
34def expand_filenames(sources):
35 """For each Path in `sources` yield either the original value (if a file)
36 or yield a list of children (if a directory) or throw an IOError (if not found)."""
37 for source in sources:
38 if source.is_file():
39 yield source
40
41 elif source.is_dir():
42 for child in source.iterdir():
43 if child.is_file():
44 yield child
45
46 else:
47 raise IOError('{source} cannot be read as a file or directory'.format(
48 source=source))
49
50
51def creatable(path):
52 """Test if `path` either a) exists and if writeable or b) can be created."""
53 # this implementation is not complete
54 if path.exists():
55 return True
56
57 if path.parent.is_dir():
58 return True
59
60 return False
61
62
63def timedelta_to_us(a):
64 """Calculate total number of microseconds in a timedelta
65
66 >>> timedelta_to_us(timedelta(days=1)) # doctest: +ELLIPSIS
67 86400000000...
68
69 This function is superflous now that timedelta has a built-in total_seconds()
70 """
71 res = (a.days * 86400 + a.seconds) * 1000000 + a.microseconds
72 return res
73
74
75def timedelta_div(a, b):
76 """Divide one timedelta by either another timedelta or by a number.
77
78 >>> timedelta_div(timedelta(hours=3), timedelta(hours=2))
79 1.5
80 """
81 if isinstance(b, timedelta):
82 return float(timedelta_to_us(a)) / float(timedelta_to_us(b))
83
84 else:
85 return timedelta(microseconds=timedelta_to_us(a) / b)
86
87
88def timedelta_mul(a, b):
89 """Multiply a timedelta by a floating point value
90
91 >>> timedelta_mul(timedelta(hours=1), 3.5)
92 datetime.timedelta(seconds=12600)
93 """
94 res = float(timedelta_to_us(a)) * b
95 return timedelta(microseconds=res)
96
97
98def nvl(obj, value_if_none=''):
99 """Return `obj` unmodified if it is not None, otherwise return `value_if_none`.
100 """
101 if obj is not None:
102 return obj
103
104 else:
105 return value_if_none
106
107
108def nvl_min(a, b):
109 """If `a` and `b` are both non-None, return min(a,b).
110
111 If either is None, return the other.
112 If both are None, return None.
113 """
114 if ((a is None or (isinstance(a, float) and math.isnan(a))) and
115 (b is None or (isinstance(b, float) and math.isnan(b)))):
116 return None
117
118 elif a is None or (isinstance(a, float) and math.isnan(a)):
119 return b
120
121 elif b is None or (isinstance(b, float) and math.isnan(b)):
122 return a
123
124 else:
125 return min(a, b)
126
127
128def nvl_max(a, b):
129 """If `a` and `b` are both non-None, return max(a,b).
130
131 If either is None, return the other.
132 If both are None, return None.
133 """
134 if ((a is None or (isinstance(a, float) and math.isnan(a))) and
135 (b is None or (isinstance(b, float) and math.isnan(b)))):
136 return None
137
138 elif a is None or (isinstance(a, float) and math.isnan(a)):
139 return b
140
141 elif b is None or (isinstance(b, float) and math.isnan(b)):
142 return a
143
144 else:
145 return max(a, b)
146
147
148def round_sf(val, sf=3):
149 """Round `val` to `sf` significant figures, returning result as a float.
150
151 >>> round_sf(12345, 3)
152 12300
153
154 >>> round_sf(0.0001546, 3)
155 0.000155
156 """
157 if val == 0.0 or math.isinf(val):
158 return val
159
160 # elif math.isinf(val):
161 # return
162
163 else:
164 return round(val, sf - int(math.floor(math.log10(abs(val)))) - 1)
165
166
167def fp_eq(a, b=0.0):
168 """Test for floating point near-equality."""
169 THRESHOLD = 0.000000000001
170 return abs(a - b) < THRESHOLD
171
172
173def dump_local_variables():
174 """Prints a list of all local variables in the calling scope."""
175 import types
176 print('Local variable dump:')
177 for k, v in sys._getframe(1).f_locals.items():
178 if k.startswith('__') or isinstance(v, (types.ModuleType, types.FunctionType)):
179 continue
180
181 print('{typ} {name} = {value}'.format(typ=type(k),
182 name=k,
183 value=v))
184
185
186def filter_dict(in_dict, keys):
187 """Return a filtered `in_dict` containing only `keys`."""
188 res = {}
189 for key in keys:
190 res[key] = in_dict[key]
191
192 return res
193
194
195def initial_matching(a, b):
196 """Return the number of initial matching items from `a` and `b`.
197
198 >>> initial_matching('farm one init', 'barm one init')
199 0
200 >>> initial_matching('farm one init', 'form one init')
201 1
202 >>> initial_matching('farm one init', 'farm bne init')
203 5
204 """
205 for i, (aa, bb) in enumerate(zip(a, b)):
206 if aa != bb:
207 return i
208
209 return i + 1
210
211
212def coalesce(*values):
213 """Return the first non-None parameter."""
214 for v in values:
215 if v is not None:
216 return v