GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.
Test Failed
Push — develop-v1.6.0 ( 9d5181...7efb31 )
by
unknown
04:49
created

MultiColumnTable.format()   F

Complexity

Conditions 28

Size

Total Lines 101

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 28
dl 0
loc 101
rs 2
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

Complexity

Complex classes like MultiColumnTable.format() often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
# Licensed to the StackStorm, Inc ('StackStorm') under one or more
2
# contributor license agreements.  See the NOTICE file distributed with
3
# this work for additional information regarding copyright ownership.
4
# The ASF licenses this file to You under the Apache License, Version 2.0
5
# (the "License"); you may not use this file except in compliance with
6
# the License.  You may obtain a copy of the License at
7
#
8
#     http://www.apache.org/licenses/LICENSE-2.0
9
#
10
# Unless required by applicable law or agreed to in writing, software
11
# distributed under the License is distributed on an "AS IS" BASIS,
12
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
# See the License for the specific language governing permissions and
14
# limitations under the License.
15
16
import json
17
import math
18
import logging
19
20
from prettytable import PrettyTable
21
from six.moves import zip
0 ignored issues
show
Bug Best Practice introduced by
This seems to re-define the built-in zip.

It is generally discouraged to redefine built-ins as this makes code very hard to read.

Loading history...
22
23
from st2client import formatters
24
from st2client.utils import strutil
25
from st2client.utils.terminal import get_terminal_size
26
27
28
LOG = logging.getLogger(__name__)
29
30
# Minimum width for the ID to make sure the ID column doesn't wrap across
31
# multiple lines
32
MIN_ID_COL_WIDTH = 26
33
34
# Minimum width for a column
35
MIN_COL_WIDTH = 5
36
37
# Default attribute display order to use if one is not provided
38
DEFAULT_ATTRIBUTE_DISPLAY_ORDER = ['id', 'name', 'pack', 'description']
39
40
# Attributes which contain bash escape sequences - we can't split those across multiple lines
41
# since things would break
42
COLORIZED_ATTRIBUTES = {
43
    'status': {
44
        'col_width': 24  # Note: len('succeed' + ' (XXXX elapsed)') <= 24
45
    }
46
}
47
48
49
class MultiColumnTable(formatters.Formatter):
50
51
    @classmethod
52
    def format(cls, entries, *args, **kwargs):
53
        attributes = kwargs.get('attributes', [])
54
        attribute_transform_functions = kwargs.get('attribute_transform_functions', {})
55
        widths = kwargs.get('widths', [])
56
        widths = widths or []
57
58
        if not widths and attributes:
59
            # Dynamically calculate column size based on the terminal size
60
            lines, cols = get_terminal_size()
61
62
            if attributes[0] == 'id':
63
                # consume iterator and save as entries so collection is accessible later.
64
                entries = [e for e in entries]
65
                # first column contains id, make sure it's not broken up
66
                first_col_width = cls._get_required_column_width(values=[e.id for e in entries],
67
                                                                 minimum_width=MIN_ID_COL_WIDTH)
68
                cols = (cols - first_col_width)
69
                col_width = int(math.floor((cols / len(attributes))))
70
            else:
71
                col_width = int(math.floor((cols / len(attributes))))
72
                first_col_width = col_width
73
74
            widths = []
75
            subtract = 0
76
            for index in range(0, len(attributes)):
77
                attribute_name = attributes[index]
78
79
                if index == 0:
80
                    widths.append(first_col_width)
81
                    continue
82
83
                if attribute_name in COLORIZED_ATTRIBUTES:
84
                    current_col_width = COLORIZED_ATTRIBUTES[attribute_name]['col_width']
85
                    subtract += (current_col_width - col_width)
86
                else:
87
                    # Make sure we subtract the added width from the last column so we account
88
                    # for the fixed width columns and make sure table is not wider than the
89
                    # terminal width.
90
                    if index == (len(attributes) - 1) and subtract:
91
                        current_col_width = (col_width - subtract)
92
93
                        if current_col_width <= MIN_COL_WIDTH:
94
                            # Make sure column width is always grater than MIN_COL_WIDTH
95
                            current_col_width = MIN_COL_WIDTH
96
                    else:
97
                        current_col_width = col_width
98
99
                widths.append(current_col_width)
100
101
        if not attributes or 'all' in attributes:
102
            entries = list(entries) if entries else []
103
104
            if len(entries) >= 1:
105
                attributes = entries[0].__dict__.keys()
106
                attributes = sorted([attr for attr in attributes if not attr.startswith('_')])
107
            else:
108
                # There are no entries so we can't infer available attributes
109
                attributes = []
110
111
        # Determine table format.
112
        if len(attributes) == len(widths):
113
            # Customize width for each column.
114
            columns = zip(attributes, widths)
115
        else:
116
            # If only 1 width value is provided then
117
            # apply it to all columns else fix at 28.
118
            width = widths[0] if len(widths) == 1 else 28
119
            columns = zip(attributes,
120
                          [width for i in range(0, len(attributes))])
121
122
        # Format result to table.
123
        table = PrettyTable()
124
        for column in columns:
125
            table.field_names.append(column[0])
126
            table.max_width[column[0]] = column[1]
127
        table.padding_width = 1
128
        table.align = 'l'
129
        table.valign = 't'
130
        for entry in entries:
131
            # TODO: Improve getting values of nested dict.
132
            values = []
133
            for field_name in table.field_names:
134
                if '.' in field_name:
135
                    field_names = field_name.split('.')
136
                    value = getattr(entry, field_names.pop(0), {})
137
                    for name in field_names:
138
                        value = cls._get_field_value(value, name)
139
                        if type(value) is str:
140
                            break
141
                    value = strutil.unescape(value)
142
                    values.append(value)
143
                else:
144
                    value = cls._get_simple_field_value(entry, field_name)
145
                    transform_function = attribute_transform_functions.get(field_name,
146
                                                                          lambda value: value)
147
                    value = transform_function(value=value)
148
                    value = strutil.unescape(value)
149
                    values.append(value)
150
            table.add_row(values)
151
        return table
152
153
    @staticmethod
154
    def _get_simple_field_value(entry, field_name):
155
        """
156
        Format a value for a simple field.
157
        """
158
        value = getattr(entry, field_name, '')
159
        if isinstance(value, (list, tuple)):
160
            if len(value) == 0:
161
                value = ''
162
            elif isinstance(value[0], (str, unicode)):
0 ignored issues
show
Comprehensibility Best Practice introduced by
Undefined variable 'unicode'
Loading history...
163
                # List contains simple string values, format it as comma
164
                # separated string
165
                value = ', '.join(value)
166
167
        return value
168
169
    @staticmethod
170
    def _get_field_value(value, field_name):
171
        r_val = value.get(field_name, None)
172
        if r_val is None:
173
            return ''
174
175
        if isinstance(r_val, list) or isinstance(r_val, dict):
176
            return r_val if len(r_val) > 0 else ''
177
        return r_val
178
179
    @staticmethod
180
    def _get_friendly_column_name(name):
181
        if not name:
182
            return None
183
184
        friendly_name = name.replace('_', ' ').replace('.', ' ').capitalize()
185
        return friendly_name
186
187
    @staticmethod
188
    def _get_required_column_width(values, minimum_width=0):
189
        max_width = len(max(values, key=len)) if values else minimum_width
190
        return max_width if max_width > minimum_width else minimum_width
191
192
193
class PropertyValueTable(formatters.Formatter):
194
195
    @classmethod
196
    def format(cls, subject, *args, **kwargs):
197
        attributes = kwargs.get('attributes', None)
198
        attribute_display_order = kwargs.get('attribute_display_order',
199
                                             DEFAULT_ATTRIBUTE_DISPLAY_ORDER)
200
        attribute_transform_functions = kwargs.get('attribute_transform_functions', {})
201
202
        if not attributes or 'all' in attributes:
203
            attributes = sorted([attr for attr in subject.__dict__
204
                                 if not attr.startswith('_')])
205
206
        for attr in attribute_display_order[::-1]:
207
            if attr in attributes:
208
                attributes.remove(attr)
209
                attributes = [attr] + attributes
210
        table = PrettyTable()
211
        table.field_names = ['Property', 'Value']
212
        table.max_width['Property'] = 20
213
        table.max_width['Value'] = 60
214
        table.padding_width = 1
215
        table.align = 'l'
216
        table.valign = 't'
217
218
        for attribute in attributes:
219
            if '.' in attribute:
220
                field_names = attribute.split('.')
221
                value = cls._get_attribute_value(subject, field_names.pop(0))
222
                for name in field_names:
223
                    value = cls._get_attribute_value(value, name)
224
                    if type(value) is str:
225
                        break
226
            else:
227
                value = cls._get_attribute_value(subject, attribute)
228
229
            transform_function = attribute_transform_functions.get(attribute,
230
                                                                   lambda value: value)
231
            value = transform_function(value=value)
232
233
            if type(value) is dict or type(value) is list:
234
                value = json.dumps(value, indent=4)
235
236
            value = strutil.unescape(value)
237
            table.add_row([attribute, value])
238
        return table
239
240
    @staticmethod
241
    def _get_attribute_value(subject, attribute):
242
        if isinstance(subject, dict):
243
            r_val = subject.get(attribute, None)
244
        else:
245
            r_val = getattr(subject, attribute, None)
246
        if r_val is None:
247
            return ''
248
        if isinstance(r_val, list) or isinstance(r_val, dict):
249
            return r_val if len(r_val) > 0 else ''
250
        return r_val
251