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/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