vhatable 1.2.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- vhatable/__init__.py +27 -0
- vhatable/cell.py +344 -0
- vhatable/cellv2.py +227 -0
- vhatable/core.py +784 -0
- vhatable/corev2.py +294 -0
- vhatable/demo.py +368 -0
- vhatable/filters.py +209 -0
- vhatable/filtersv2.py +121 -0
- vhatable/processors.py +635 -0
- vhatable/sample.py +666 -0
- vhatable/samplev2.py +212 -0
- vhatable/units.py +430 -0
- vhatable-1.2.1.dist-info/METADATA +66 -0
- vhatable-1.2.1.dist-info/RECORD +16 -0
- vhatable-1.2.1.dist-info/WHEEL +4 -0
- vhatable-1.2.1.dist-info/licenses/LICENSE.txt +674 -0
vhatable/core.py
ADDED
|
@@ -0,0 +1,784 @@
|
|
|
1
|
+
#! /usr/bin/env python
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""TODO"""
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
# This file is part of Vhatable api.
|
|
7
|
+
#
|
|
8
|
+
# Vhatable api is free software: you can redistribute it and/or modify
|
|
9
|
+
# it under the terms of the GNU General Public License as published by
|
|
10
|
+
# the Free Software Foundation, either version 3 of the License, or
|
|
11
|
+
# (at your option) any later version.
|
|
12
|
+
#
|
|
13
|
+
# Vhatable api is distributed in the hope that it will be useful,
|
|
14
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
15
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
16
|
+
# GNU General Public License for more details.
|
|
17
|
+
#
|
|
18
|
+
# You should have received a copy of the GNU General Public License
|
|
19
|
+
# along with Vhatable api. If not, see <http://www.gnu.org/licenses/>.
|
|
20
|
+
#
|
|
21
|
+
# Copyright 2014 Frédéric MARTIN
|
|
22
|
+
#
|
|
23
|
+
# Contributors list :
|
|
24
|
+
#
|
|
25
|
+
# Frédéric MARTIN frederic.martin.fma@gmail.com
|
|
26
|
+
#
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
# pylint: skip-file
|
|
30
|
+
# flake8: noqa
|
|
31
|
+
# Deprecated module
|
|
32
|
+
|
|
33
|
+
import json
|
|
34
|
+
import time
|
|
35
|
+
import logging
|
|
36
|
+
import logging.handlers
|
|
37
|
+
|
|
38
|
+
from collections import OrderedDict
|
|
39
|
+
from functools import wraps
|
|
40
|
+
from veryprettytable import VeryPrettyTable
|
|
41
|
+
from .cell import CellFactory
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class Time:
|
|
45
|
+
"""TODO"""
|
|
46
|
+
|
|
47
|
+
# pylint: disable=too-few-public-methods
|
|
48
|
+
|
|
49
|
+
def __init__(self, logger_name, label="execution time : %(time)s"):
|
|
50
|
+
self.log = logging.getLogger(logger_name)
|
|
51
|
+
self.enabled = False
|
|
52
|
+
self.label = label
|
|
53
|
+
|
|
54
|
+
def __call__(self, original_func):
|
|
55
|
+
@wraps(original_func)
|
|
56
|
+
def time_wrapper(*args, **kwargs):
|
|
57
|
+
self.log.debug("args: %s", args)
|
|
58
|
+
self.log.debug("kwargs: %s", kwargs)
|
|
59
|
+
start = time.time()
|
|
60
|
+
res = original_func(*args, **kwargs)
|
|
61
|
+
end = time.time()
|
|
62
|
+
diff = end - start
|
|
63
|
+
# enforce string length to avoid random length
|
|
64
|
+
diff = f"{diff:.15f}"
|
|
65
|
+
if len(args) > 0:
|
|
66
|
+
first_arg = args[0]
|
|
67
|
+
self.enabled = getattr(first_arg, "verbose", self.enabled)
|
|
68
|
+
self.log.debug(self.label, {"time": diff})
|
|
69
|
+
if self.enabled:
|
|
70
|
+
print(self.label % {"time": diff})
|
|
71
|
+
return res
|
|
72
|
+
|
|
73
|
+
return time_wrapper
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
class AbstractTable:
|
|
77
|
+
"""TODO"""
|
|
78
|
+
|
|
79
|
+
DEFAULT_TOTAL = "\nRessources found : %(count)s"
|
|
80
|
+
|
|
81
|
+
log = None
|
|
82
|
+
vertical = False
|
|
83
|
+
csv = False
|
|
84
|
+
json = False
|
|
85
|
+
debug = 0
|
|
86
|
+
verbose = False
|
|
87
|
+
extended = False
|
|
88
|
+
raw = False
|
|
89
|
+
no_cell = False
|
|
90
|
+
cli_mode = False
|
|
91
|
+
cfa = CellFactory(False, False, 0)
|
|
92
|
+
keys = []
|
|
93
|
+
args = None
|
|
94
|
+
cli = None
|
|
95
|
+
endpoint = None
|
|
96
|
+
_filters = []
|
|
97
|
+
_pre_render_classes = []
|
|
98
|
+
|
|
99
|
+
def filters(self, row, filters):
|
|
100
|
+
"""TODO"""
|
|
101
|
+
if filters is not None:
|
|
102
|
+
if isinstance(filters, list):
|
|
103
|
+
matches = 0
|
|
104
|
+
enabled_filters = 0
|
|
105
|
+
for func in filters:
|
|
106
|
+
self.log.debug("filter: %s (enabled=%s)", func, func.is_enable())
|
|
107
|
+
if func.is_enable():
|
|
108
|
+
enabled_filters += 1
|
|
109
|
+
if func(row):
|
|
110
|
+
matches += 1
|
|
111
|
+
self.log.debug("matches: %s", matches)
|
|
112
|
+
self.log.debug("enabled_filters: %s", enabled_filters)
|
|
113
|
+
if enabled_filters == 0:
|
|
114
|
+
return True
|
|
115
|
+
if matches == enabled_filters:
|
|
116
|
+
return True
|
|
117
|
+
return False
|
|
118
|
+
if filters.is_enable():
|
|
119
|
+
if filters(row):
|
|
120
|
+
return True
|
|
121
|
+
return False
|
|
122
|
+
return True
|
|
123
|
+
|
|
124
|
+
def get_raw(self):
|
|
125
|
+
"""TODO"""
|
|
126
|
+
raise NotImplementedError()
|
|
127
|
+
|
|
128
|
+
def get_json(self):
|
|
129
|
+
"""TODO"""
|
|
130
|
+
raise NotImplementedError()
|
|
131
|
+
|
|
132
|
+
def get_csv(self):
|
|
133
|
+
"""TODO"""
|
|
134
|
+
raise NotImplementedError()
|
|
135
|
+
|
|
136
|
+
def load(self, data, filters=None):
|
|
137
|
+
"""TODO"""
|
|
138
|
+
raise NotImplementedError()
|
|
139
|
+
|
|
140
|
+
def load_v2(self, json_list):
|
|
141
|
+
"""Load list of json objects into the table"""
|
|
142
|
+
self.load(json_list, self._filters)
|
|
143
|
+
return self
|
|
144
|
+
|
|
145
|
+
def _transform_to_cell(self, json_row, off=False):
|
|
146
|
+
"""TODO"""
|
|
147
|
+
if off:
|
|
148
|
+
return json_row
|
|
149
|
+
if self.debug >= 2:
|
|
150
|
+
self.log.debug("begin row")
|
|
151
|
+
row_full = OrderedDict()
|
|
152
|
+
row_light = OrderedDict()
|
|
153
|
+
keys = list(self.keys)
|
|
154
|
+
keys += list(json_row.keys())
|
|
155
|
+
keys = list(OrderedDict.fromkeys(keys))
|
|
156
|
+
if self.debug >= 2:
|
|
157
|
+
self.log.debug("all keys: %s ", keys)
|
|
158
|
+
for key in keys:
|
|
159
|
+
value = None
|
|
160
|
+
if key in json_row:
|
|
161
|
+
value = json_row[key]
|
|
162
|
+
else:
|
|
163
|
+
self.log.debug("key not found: %s", key)
|
|
164
|
+
if self.debug >= 2:
|
|
165
|
+
self.log.debug("key: %s ", key)
|
|
166
|
+
cell = self.cfa(key, value, row_full)
|
|
167
|
+
# FIXME
|
|
168
|
+
cell.hidden = False
|
|
169
|
+
cell.extended = self.extended
|
|
170
|
+
row_full[key] = cell
|
|
171
|
+
if key in self.keys:
|
|
172
|
+
row_light[key] = cell
|
|
173
|
+
else:
|
|
174
|
+
cell.hidden = True
|
|
175
|
+
if self.debug >= 2:
|
|
176
|
+
self.log.debug("end row")
|
|
177
|
+
return row_light
|
|
178
|
+
|
|
179
|
+
def render(self):
|
|
180
|
+
"""TODO"""
|
|
181
|
+
raise NotImplementedError()
|
|
182
|
+
|
|
183
|
+
def get_string(self):
|
|
184
|
+
"""TODO"""
|
|
185
|
+
raise NotImplementedError()
|
|
186
|
+
|
|
187
|
+
def pprint(self, msg, meta=None):
|
|
188
|
+
"""TODO"""
|
|
189
|
+
if meta:
|
|
190
|
+
msg = msg % meta
|
|
191
|
+
self.log.debug(msg)
|
|
192
|
+
print(msg)
|
|
193
|
+
|
|
194
|
+
def _display_nb_elts(self):
|
|
195
|
+
"""TODO"""
|
|
196
|
+
if self.verbose:
|
|
197
|
+
meta = {"count": len(self.get_raw())}
|
|
198
|
+
self.pprint(self.DEFAULT_TOTAL, meta)
|
|
199
|
+
return True
|
|
200
|
+
|
|
201
|
+
def _pre_render(self):
|
|
202
|
+
"""Trigger some classes before rendering filtered data."""
|
|
203
|
+
for clazz in self._pre_render_classes:
|
|
204
|
+
self.log.debug(clazz)
|
|
205
|
+
clazz(self.args, self.cli, self.endpoint, self.get_raw())
|
|
206
|
+
|
|
207
|
+
def add_pre_render_class(self, clazz):
|
|
208
|
+
"""TODO"""
|
|
209
|
+
self._pre_render_classes.append(clazz)
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
class BaseTable(AbstractTable):
|
|
213
|
+
"""TODO"""
|
|
214
|
+
|
|
215
|
+
# pylint: disable=too-many-instance-attributes
|
|
216
|
+
vertical = True
|
|
217
|
+
start = 0
|
|
218
|
+
end = 0
|
|
219
|
+
_pref_start = 0
|
|
220
|
+
_pref_end = 0
|
|
221
|
+
_pref_limit = 0
|
|
222
|
+
raw_json = False
|
|
223
|
+
_pref_no_csv_headers = False
|
|
224
|
+
|
|
225
|
+
def __init__(self, keys=[], reverse=False, debug=0):
|
|
226
|
+
self.debug = debug
|
|
227
|
+
classname = str(self.__class__.__name__.lower())
|
|
228
|
+
self.log = logging.getLogger("vhatable." + classname)
|
|
229
|
+
self.keys = keys
|
|
230
|
+
# field only use for compatibility with HTable
|
|
231
|
+
self.align = {}
|
|
232
|
+
self.start = None
|
|
233
|
+
self.end = None
|
|
234
|
+
self._rows = []
|
|
235
|
+
self._maxlengthkey = 0
|
|
236
|
+
self.reversesort = reverse
|
|
237
|
+
self.no_cell = False
|
|
238
|
+
for k in keys:
|
|
239
|
+
self.sortby = k
|
|
240
|
+
break
|
|
241
|
+
self.log.debug("sortby(first column): %s", self.sortby)
|
|
242
|
+
|
|
243
|
+
def load(self, data, filters=None):
|
|
244
|
+
# pylint: disable=unused-argument
|
|
245
|
+
# Only for compatibility with older lines of code.
|
|
246
|
+
"""TODO"""
|
|
247
|
+
self.log.debug("keys: %s", self.keys)
|
|
248
|
+
for row in data:
|
|
249
|
+
row = self._transform_to_cell(row, self.no_cell)
|
|
250
|
+
if self.filters(row, filters):
|
|
251
|
+
self.add_row(row)
|
|
252
|
+
if self._pref_start > 0:
|
|
253
|
+
self.start = self._pref_start
|
|
254
|
+
limit = self._pref_limit
|
|
255
|
+
if limit > 0:
|
|
256
|
+
self.end = self.start + limit
|
|
257
|
+
elif self._pref_end > 0:
|
|
258
|
+
self.start = len(self._rows) - self._pref_end
|
|
259
|
+
limit = self._pref_limit
|
|
260
|
+
if limit > 0:
|
|
261
|
+
self.end = self.start + limit
|
|
262
|
+
elif self._pref_limit > 0:
|
|
263
|
+
self.start = 0
|
|
264
|
+
self.end = 0 + self._pref_limit
|
|
265
|
+
|
|
266
|
+
def add_row(self, row):
|
|
267
|
+
"""TODO"""
|
|
268
|
+
if self.debug >= 2:
|
|
269
|
+
self.log.debug(row)
|
|
270
|
+
if not isinstance(row, dict):
|
|
271
|
+
raise ValueError("every row should be a dict")
|
|
272
|
+
self._rows.append(row)
|
|
273
|
+
|
|
274
|
+
def get_raw(self):
|
|
275
|
+
"""TODO"""
|
|
276
|
+
if self.sortby:
|
|
277
|
+
try:
|
|
278
|
+
self._rows = sorted(
|
|
279
|
+
self._rows, reverse=self.reversesort, key=lambda x: x.get(self.sortby)
|
|
280
|
+
)
|
|
281
|
+
except KeyError as ex:
|
|
282
|
+
self.log.warning("missing sortby key : %s", ex)
|
|
283
|
+
source = self._rows
|
|
284
|
+
if self.start:
|
|
285
|
+
source = source[self.start :]
|
|
286
|
+
if self.end:
|
|
287
|
+
source = source[: self.end - self.start]
|
|
288
|
+
elif self.end:
|
|
289
|
+
source = source[: self.end]
|
|
290
|
+
return source
|
|
291
|
+
|
|
292
|
+
def get_json(self):
|
|
293
|
+
"""TODO"""
|
|
294
|
+
records = []
|
|
295
|
+
if self.raw_json:
|
|
296
|
+
return json.dumps(self.get_raw(), sort_keys=True, indent=2)
|
|
297
|
+
for row in self.get_raw():
|
|
298
|
+
record = {}
|
|
299
|
+
for k in self.keys:
|
|
300
|
+
record[k] = row.get(k)
|
|
301
|
+
records.append(record)
|
|
302
|
+
return json.dumps(records, sort_keys=True, indent=2)
|
|
303
|
+
|
|
304
|
+
def get_csv(self):
|
|
305
|
+
"""TODO"""
|
|
306
|
+
records = []
|
|
307
|
+
if not self._pref_no_csv_headers:
|
|
308
|
+
records.append(";".join(self.keys))
|
|
309
|
+
for row in self.get_raw():
|
|
310
|
+
record = []
|
|
311
|
+
for k in self.keys:
|
|
312
|
+
data = row.get(k)
|
|
313
|
+
if isinstance(data, str):
|
|
314
|
+
record.append(data)
|
|
315
|
+
else:
|
|
316
|
+
data_str = str(data)
|
|
317
|
+
record.append(data_str)
|
|
318
|
+
records.append(";".join(record))
|
|
319
|
+
return "\n".join(records)
|
|
320
|
+
|
|
321
|
+
@Time("vhatable.core.render", label="render time : %(time)s")
|
|
322
|
+
def render(self):
|
|
323
|
+
"""TODO"""
|
|
324
|
+
if self.json:
|
|
325
|
+
print(self.get_json())
|
|
326
|
+
return True
|
|
327
|
+
if self.csv:
|
|
328
|
+
print(self.get_csv())
|
|
329
|
+
return True
|
|
330
|
+
out = self.get_string()
|
|
331
|
+
self._pre_render()
|
|
332
|
+
print(str(out))
|
|
333
|
+
self._display_nb_elts()
|
|
334
|
+
return True
|
|
335
|
+
|
|
336
|
+
|
|
337
|
+
class VTable(BaseTable):
|
|
338
|
+
"""TODO"""
|
|
339
|
+
|
|
340
|
+
vertical = True
|
|
341
|
+
|
|
342
|
+
def get_string(self):
|
|
343
|
+
"""TODO"""
|
|
344
|
+
max_length_line = 0
|
|
345
|
+
records = []
|
|
346
|
+
classname = str(self.__class__.__name__.lower())
|
|
347
|
+
self.log = logging.getLogger(classname)
|
|
348
|
+
for row in self.get_raw():
|
|
349
|
+
record = []
|
|
350
|
+
for k in self.keys:
|
|
351
|
+
try:
|
|
352
|
+
slength = str(self._maxlengthkey)
|
|
353
|
+
t_format = "{key:" + slength + "s} | {value:s}"
|
|
354
|
+
dataa = None
|
|
355
|
+
column_data = row.get(k)
|
|
356
|
+
if isinstance(column_data, str):
|
|
357
|
+
dataa = {"key": k, "value": column_data}
|
|
358
|
+
else:
|
|
359
|
+
column_data_str = str(column_data)
|
|
360
|
+
dataa = {"key": k, "value": column_data_str}
|
|
361
|
+
t_record = t_format.format(**dataa)
|
|
362
|
+
record.append(t_record)
|
|
363
|
+
max_length_line = max(max_length_line, len(t_record))
|
|
364
|
+
except UnicodeEncodeError as ex:
|
|
365
|
+
self.log.error("UnicodeEncodeError: %s", ex)
|
|
366
|
+
dataa = {"key": k, "value": "UnicodeEncodeError"}
|
|
367
|
+
t_record = str(t_format).format(**dataa)
|
|
368
|
+
record.append(t_record)
|
|
369
|
+
records.append("\n".join(record))
|
|
370
|
+
out = []
|
|
371
|
+
cptline = 0
|
|
372
|
+
for record in records:
|
|
373
|
+
cptline += 1
|
|
374
|
+
header = "-[ RECORD " + str(cptline) + " ]-"
|
|
375
|
+
# pylint: disable=unused-variable
|
|
376
|
+
range_limit = max_length_line - len(header)
|
|
377
|
+
header += "".join(["-" for i in range(range_limit)])
|
|
378
|
+
out.append(header)
|
|
379
|
+
out.append(record)
|
|
380
|
+
return "\n".join(out)
|
|
381
|
+
|
|
382
|
+
def add_row(self, row):
|
|
383
|
+
"""TODO"""
|
|
384
|
+
super().add_row(row)
|
|
385
|
+
self.update_max_lengthkey(row)
|
|
386
|
+
|
|
387
|
+
def update_max_lengthkey(self, row):
|
|
388
|
+
"""TODO"""
|
|
389
|
+
for k in row:
|
|
390
|
+
self._maxlengthkey = max((len(repr(k)), self._maxlengthkey))
|
|
391
|
+
|
|
392
|
+
|
|
393
|
+
class ConsoleTable(BaseTable):
|
|
394
|
+
"""TODO"""
|
|
395
|
+
|
|
396
|
+
vertical = False
|
|
397
|
+
|
|
398
|
+
def get_string(self):
|
|
399
|
+
self.log.debug("Not Implemented.")
|
|
400
|
+
|
|
401
|
+
@Time("vhatable.core.render", label="render time : %(time)s")
|
|
402
|
+
def render(self):
|
|
403
|
+
"""TODO"""
|
|
404
|
+
if self.json:
|
|
405
|
+
print(self.get_json())
|
|
406
|
+
return True
|
|
407
|
+
if self.csv:
|
|
408
|
+
print(self.get_csv())
|
|
409
|
+
return True
|
|
410
|
+
self._pre_render()
|
|
411
|
+
for row in self.get_raw():
|
|
412
|
+
record = []
|
|
413
|
+
for k in self.keys:
|
|
414
|
+
try:
|
|
415
|
+
t_format = "{value:s}"
|
|
416
|
+
column_data = row.get(k)
|
|
417
|
+
if isinstance(column_data, str):
|
|
418
|
+
t_record = t_format.format(value=column_data)
|
|
419
|
+
else:
|
|
420
|
+
t_record = t_format.format(value=column_data)
|
|
421
|
+
record.append(t_record)
|
|
422
|
+
except UnicodeEncodeError as ex:
|
|
423
|
+
self.log.error("UnicodeEncodeError: %s", ex)
|
|
424
|
+
record.append("UnicodeEncodeError")
|
|
425
|
+
print(str(" ".join(record)))
|
|
426
|
+
self._display_nb_elts()
|
|
427
|
+
return True
|
|
428
|
+
|
|
429
|
+
|
|
430
|
+
class HTable(BaseTable):
|
|
431
|
+
"""TODO"""
|
|
432
|
+
|
|
433
|
+
# pylint: disable=too-many-instance-attributes
|
|
434
|
+
|
|
435
|
+
def get_string(self):
|
|
436
|
+
"""TODO"""
|
|
437
|
+
table = VeryPrettyTable()
|
|
438
|
+
table.field_names = self.keys
|
|
439
|
+
table.align = "l"
|
|
440
|
+
for row in self.get_raw():
|
|
441
|
+
data = []
|
|
442
|
+
for colum in self.keys:
|
|
443
|
+
data.append(row.get(colum))
|
|
444
|
+
table.add_row(data)
|
|
445
|
+
return table.get_string()
|
|
446
|
+
|
|
447
|
+
|
|
448
|
+
class Action:
|
|
449
|
+
"""TODO"""
|
|
450
|
+
|
|
451
|
+
# we must convert row to cells, otherwise filters won't work.
|
|
452
|
+
# Filters are expecting cells, not raw data.
|
|
453
|
+
# Changing from True to False will break every already existing actions
|
|
454
|
+
versions = {1: {"no_cell": True}, 2: {"no_cell": False}}
|
|
455
|
+
# Default version will be 1 to avoid breaking changes.
|
|
456
|
+
version = 1
|
|
457
|
+
|
|
458
|
+
def __getattr__(self, attr):
|
|
459
|
+
if attr == "no_cell":
|
|
460
|
+
return self.versions[self.version][attr]
|
|
461
|
+
raise AttributeError(attr)
|
|
462
|
+
|
|
463
|
+
def __init__(self):
|
|
464
|
+
self.cli_mode = False
|
|
465
|
+
self.verbose = False
|
|
466
|
+
self.dry_run = False
|
|
467
|
+
self.debug = 0
|
|
468
|
+
classname = str(self.__class__.__name__.lower())
|
|
469
|
+
self.log = logging.getLogger("vhatable." + classname)
|
|
470
|
+
self.cli = None
|
|
471
|
+
self.endpoint = None
|
|
472
|
+
|
|
473
|
+
def init(self, args, cli, endpoint):
|
|
474
|
+
"""Init object members with values in args object"""
|
|
475
|
+
self.cli = cli
|
|
476
|
+
self.endpoint = endpoint
|
|
477
|
+
for att in ["cli_mode", "verbose", "debug", "dry_run"]:
|
|
478
|
+
if hasattr(args, att):
|
|
479
|
+
setattr(self, att, getattr(args, att))
|
|
480
|
+
|
|
481
|
+
def pprint(self, msg, meta=None):
|
|
482
|
+
"""TODO"""
|
|
483
|
+
if meta:
|
|
484
|
+
msg = msg % meta
|
|
485
|
+
self.log.debug(msg)
|
|
486
|
+
print(msg)
|
|
487
|
+
|
|
488
|
+
def pprint_warn(self, msg, meta=None):
|
|
489
|
+
"""TODO"""
|
|
490
|
+
if meta is None:
|
|
491
|
+
meta = {}
|
|
492
|
+
msg = "WARN: " + msg % meta
|
|
493
|
+
self.log.warning(msg)
|
|
494
|
+
print(msg)
|
|
495
|
+
|
|
496
|
+
def pprint_error(self, msg, meta=None):
|
|
497
|
+
"""TODO"""
|
|
498
|
+
if meta is None:
|
|
499
|
+
meta = {}
|
|
500
|
+
msg = "ERROR: " + msg % meta
|
|
501
|
+
self.log.error(msg)
|
|
502
|
+
print(msg)
|
|
503
|
+
|
|
504
|
+
def pretty_json(self, obj):
|
|
505
|
+
"""Just a pretty printer for a json object."""
|
|
506
|
+
# pylint: disable=no-self-use
|
|
507
|
+
print((json.dumps(obj, sort_keys=True, indent=2)))
|
|
508
|
+
|
|
509
|
+
def __call__(self, args, cli, endpoint, data):
|
|
510
|
+
raise NotImplementedError()
|
|
511
|
+
|
|
512
|
+
|
|
513
|
+
class ActionV2(Action):
|
|
514
|
+
"""TODO"""
|
|
515
|
+
|
|
516
|
+
# pylint: disable=too-few-public-methods
|
|
517
|
+
|
|
518
|
+
version = 2
|
|
519
|
+
|
|
520
|
+
|
|
521
|
+
class CountAction(ActionV2):
|
|
522
|
+
"""TODO"""
|
|
523
|
+
|
|
524
|
+
# pylint: disable=too-few-public-methods
|
|
525
|
+
|
|
526
|
+
DEFAULT_TOTAL = "Ressources found : %(count)s"
|
|
527
|
+
|
|
528
|
+
def __call__(self, args, cli, endpoint, data):
|
|
529
|
+
"""TODO"""
|
|
530
|
+
self.init(args, cli, endpoint)
|
|
531
|
+
if self.cli_mode:
|
|
532
|
+
print((len(data)))
|
|
533
|
+
else:
|
|
534
|
+
meta = {"count": len(data)}
|
|
535
|
+
self.pprint(self.DEFAULT_TOTAL, meta)
|
|
536
|
+
return True
|
|
537
|
+
|
|
538
|
+
|
|
539
|
+
class CliModeAction(Action):
|
|
540
|
+
"""TODO"""
|
|
541
|
+
|
|
542
|
+
# pylint: disable=too-few-public-methods
|
|
543
|
+
|
|
544
|
+
def __init__(self, identifier="uuid"):
|
|
545
|
+
super().__init__()
|
|
546
|
+
self.identifier = identifier
|
|
547
|
+
|
|
548
|
+
def __call__(self, args, cli, endpoint, data):
|
|
549
|
+
"""TODO"""
|
|
550
|
+
self.init(args, cli, endpoint)
|
|
551
|
+
for row in data:
|
|
552
|
+
print((str(row.get(self.identifier))))
|
|
553
|
+
return True
|
|
554
|
+
|
|
555
|
+
|
|
556
|
+
class SampleAction(Action):
|
|
557
|
+
"""TODO"""
|
|
558
|
+
|
|
559
|
+
# pylint: disable=too-many-instance-attributes
|
|
560
|
+
|
|
561
|
+
def __init__(self, name):
|
|
562
|
+
super().__init__()
|
|
563
|
+
self.name = name
|
|
564
|
+
|
|
565
|
+
def __call__(self, args, cli, endpoint, data):
|
|
566
|
+
"""TODO"""
|
|
567
|
+
self.init(args, cli, endpoint)
|
|
568
|
+
print(("ACTION:", self.name))
|
|
569
|
+
print("CLI:", cli)
|
|
570
|
+
print("endpoint:", endpoint)
|
|
571
|
+
print(">--- Filtered data ----")
|
|
572
|
+
for row in data:
|
|
573
|
+
print(row)
|
|
574
|
+
print("---- Filtered data ---<")
|
|
575
|
+
return True
|
|
576
|
+
|
|
577
|
+
|
|
578
|
+
class ActionTable(VTable):
|
|
579
|
+
"""TODO"""
|
|
580
|
+
|
|
581
|
+
# pylint: disable=too-many-instance-attributes
|
|
582
|
+
|
|
583
|
+
action = Action()
|
|
584
|
+
|
|
585
|
+
# @Time('vhatable.core.render', label='render time : %(time)s')
|
|
586
|
+
def render(self):
|
|
587
|
+
"""Call the action method with filtered data."""
|
|
588
|
+
return self.action(self.args, self.cli, self.endpoint, self.get_raw())
|
|
589
|
+
|
|
590
|
+
|
|
591
|
+
class TableFactory:
|
|
592
|
+
"""TODO"""
|
|
593
|
+
|
|
594
|
+
# pylint: disable=too-many-instance-attributes
|
|
595
|
+
|
|
596
|
+
def __init__(
|
|
597
|
+
self,
|
|
598
|
+
cli,
|
|
599
|
+
endpoint,
|
|
600
|
+
default_sort_column=None,
|
|
601
|
+
default_actions=True,
|
|
602
|
+
cli_mode_identifier="uuid",
|
|
603
|
+
):
|
|
604
|
+
"""TODO"""
|
|
605
|
+
# pylint: disable=too-many-arguments
|
|
606
|
+
classname = str(self.__class__.__name__.lower())
|
|
607
|
+
self.log = logging.getLogger("vhatable." + classname)
|
|
608
|
+
self.cli = cli
|
|
609
|
+
self.endpoint = endpoint
|
|
610
|
+
self.args = None
|
|
611
|
+
self.columns = None
|
|
612
|
+
self.fields = None
|
|
613
|
+
self.cli_mode = False
|
|
614
|
+
self.cli_mode_identifier = cli_mode_identifier
|
|
615
|
+
self.default_sort_column = default_sort_column
|
|
616
|
+
self.vertical = False
|
|
617
|
+
self.json = False
|
|
618
|
+
self.raw = False
|
|
619
|
+
self.raw_json = False
|
|
620
|
+
self.csv = False
|
|
621
|
+
self.sort_by = None
|
|
622
|
+
self.reverse = False
|
|
623
|
+
self.extended = False
|
|
624
|
+
self.no_cell = False
|
|
625
|
+
self.debug = 0
|
|
626
|
+
self.start = 0
|
|
627
|
+
self.end = 0
|
|
628
|
+
self.limit = 0
|
|
629
|
+
self.no_headers = False
|
|
630
|
+
self._vertical_clazz = VTable
|
|
631
|
+
self._horizontal_clazz = HTable
|
|
632
|
+
self._action_classes = OrderedDict()
|
|
633
|
+
self._action_table = ActionTable
|
|
634
|
+
if default_actions:
|
|
635
|
+
self._action_classes["count_only"] = CountAction()
|
|
636
|
+
self._custom_cells = {}
|
|
637
|
+
self.filters = []
|
|
638
|
+
self._pre_render_classes = []
|
|
639
|
+
|
|
640
|
+
@property
|
|
641
|
+
def horizontal_clazz(self):
|
|
642
|
+
"""TODO"""
|
|
643
|
+
return self._horizontal_clazz
|
|
644
|
+
|
|
645
|
+
@horizontal_clazz.setter
|
|
646
|
+
def horizontal_clazz(self, horizontal_clazz):
|
|
647
|
+
"""TODO"""
|
|
648
|
+
self._horizontal_clazz = horizontal_clazz
|
|
649
|
+
|
|
650
|
+
def load_args(self, args):
|
|
651
|
+
"""load builder attributes from args."""
|
|
652
|
+
attrs = [
|
|
653
|
+
"vertical",
|
|
654
|
+
"json",
|
|
655
|
+
"raw",
|
|
656
|
+
"raw_json",
|
|
657
|
+
"csv",
|
|
658
|
+
"sort_by",
|
|
659
|
+
"reverse",
|
|
660
|
+
"extended",
|
|
661
|
+
"no_cell",
|
|
662
|
+
"verbose",
|
|
663
|
+
"cli_mode",
|
|
664
|
+
"no_headers",
|
|
665
|
+
"debug",
|
|
666
|
+
"start",
|
|
667
|
+
"end",
|
|
668
|
+
"limit",
|
|
669
|
+
"fields",
|
|
670
|
+
]
|
|
671
|
+
for attr in attrs:
|
|
672
|
+
if hasattr(args, attr):
|
|
673
|
+
setattr(self, attr, getattr(args, attr))
|
|
674
|
+
self.args = args
|
|
675
|
+
return self
|
|
676
|
+
|
|
677
|
+
def add_custom_cell(self, column, clazz):
|
|
678
|
+
"""Add specific cell class to format a column."""
|
|
679
|
+
self._custom_cells[column] = clazz
|
|
680
|
+
|
|
681
|
+
def add_action(self, flag, clazz):
|
|
682
|
+
"""Add some custom action class trigger by a flag."""
|
|
683
|
+
self._action_classes[flag] = clazz
|
|
684
|
+
|
|
685
|
+
def add_filter_cond(self, condition, *filters):
|
|
686
|
+
"""Add some filters only if condition is true"""
|
|
687
|
+
if condition:
|
|
688
|
+
for filterr in filters:
|
|
689
|
+
self.filters.append(filterr)
|
|
690
|
+
|
|
691
|
+
def add_filters(self, *filters):
|
|
692
|
+
"""Add some filters."""
|
|
693
|
+
for filterr in filters:
|
|
694
|
+
self.filters.append(filterr)
|
|
695
|
+
|
|
696
|
+
def add_pre_render_class(self, clazz):
|
|
697
|
+
"""TODO"""
|
|
698
|
+
self._pre_render_classes.append(clazz)
|
|
699
|
+
|
|
700
|
+
def build(self):
|
|
701
|
+
# pylint: disable=too-many-branches
|
|
702
|
+
# pylint: disable=too-many-statements
|
|
703
|
+
# pylint: disable=too-many-locals
|
|
704
|
+
# This method is a little bit diry, need some refactoring.
|
|
705
|
+
"""Build table object"""
|
|
706
|
+
if self.json or self.csv:
|
|
707
|
+
self.vertical = True
|
|
708
|
+
if self.json:
|
|
709
|
+
self.raw = True
|
|
710
|
+
self.no_cell = True
|
|
711
|
+
columns = self.fields
|
|
712
|
+
if not columns:
|
|
713
|
+
if not self.columns:
|
|
714
|
+
if hasattr(self.endpoint, "get_rbu"):
|
|
715
|
+
columns = self.endpoint.get_rbu().get_keys(self.extended)
|
|
716
|
+
else:
|
|
717
|
+
columns = self.columns
|
|
718
|
+
if not columns:
|
|
719
|
+
raise ValueError("Missing columns")
|
|
720
|
+
table = None
|
|
721
|
+
action_classes = OrderedDict(self._action_classes)
|
|
722
|
+
action_classes["cli_mode"] = CliModeAction(self.cli_mode_identifier)
|
|
723
|
+
self.log.debug("action_classes: %s", action_classes)
|
|
724
|
+
for flag, action in list(action_classes.items()):
|
|
725
|
+
if getattr(self.args, flag, False):
|
|
726
|
+
table = self._action_table(columns)
|
|
727
|
+
# a little bit ugly. :(
|
|
728
|
+
table.action = action
|
|
729
|
+
# if no_cell property does not exist, we keep the old behaviour
|
|
730
|
+
self.no_cell = getattr(action, "no_cell", True)
|
|
731
|
+
self.raw = True
|
|
732
|
+
break
|
|
733
|
+
if table is None:
|
|
734
|
+
if self.vertical:
|
|
735
|
+
table = self._vertical_clazz(columns)
|
|
736
|
+
else:
|
|
737
|
+
table = self._horizontal_clazz(columns)
|
|
738
|
+
table.padding_width = 1
|
|
739
|
+
for clazz in self._pre_render_classes:
|
|
740
|
+
table.add_pre_render_class(clazz)
|
|
741
|
+
attrs = [
|
|
742
|
+
"vertical",
|
|
743
|
+
"json",
|
|
744
|
+
"raw",
|
|
745
|
+
"raw_json",
|
|
746
|
+
"csv",
|
|
747
|
+
"cli",
|
|
748
|
+
"endpoint",
|
|
749
|
+
"reverse",
|
|
750
|
+
"extended",
|
|
751
|
+
"no_cell",
|
|
752
|
+
"debug",
|
|
753
|
+
"verbose",
|
|
754
|
+
"cli_mode",
|
|
755
|
+
]
|
|
756
|
+
for attr in attrs:
|
|
757
|
+
setattr(table, attr, getattr(self, attr))
|
|
758
|
+
if self.sort_by is None:
|
|
759
|
+
if self.default_sort_column:
|
|
760
|
+
if self.default_sort_column in columns:
|
|
761
|
+
table.sortby = self.default_sort_column
|
|
762
|
+
else:
|
|
763
|
+
if self.sort_by in columns:
|
|
764
|
+
table.sortby = self.sort_by
|
|
765
|
+
self.log.debug("default_sort_column: %s", self.default_sort_column)
|
|
766
|
+
self.log.debug("sort_by: %s", self.sort_by)
|
|
767
|
+
self.log.debug("final sortby: %s", table.sortby)
|
|
768
|
+
# very ugly. need big refactoring of all tables.
|
|
769
|
+
table.reversesort = self.reverse
|
|
770
|
+
table._pref_start = self.start
|
|
771
|
+
table._pref_end = self.end
|
|
772
|
+
table._pref_limit = self.limit
|
|
773
|
+
table._pref_no_csv_headers = self.no_headers
|
|
774
|
+
if self._custom_cells:
|
|
775
|
+
for column, clazz in list(self._custom_cells.items()):
|
|
776
|
+
table.cfa.custom_cells[column] = clazz
|
|
777
|
+
table.cfa.raw = self.raw
|
|
778
|
+
table.cfa.vertical = self.vertical
|
|
779
|
+
table.cfa.debug = self.debug
|
|
780
|
+
table._filters = self.filters
|
|
781
|
+
# compat
|
|
782
|
+
table.args = self.args
|
|
783
|
+
table.keys = columns
|
|
784
|
+
return table
|