Completed
Push — master ( 7f3a96...4d9cef )
by Kent
13s
created

SectionItems   D

Complexity

Total Complexity 58

Size/Duplication

Total Lines 235
Duplicated Lines 0 %

Importance

Changes 5
Bugs 3 Features 0
Metric Value
c 5
b 3
f 0
dl 0
loc 235
rs 4.8387
wmc 58

20 Methods

Rating   Name   Duplication   Size   Complexity  
A keys() 0 3 2
A values() 0 3 1
A iterkeys() 0 2 1
A itervalues() 0 2 1
A items() 0 3 2
A iteritems() 0 2 1
B __contains__() 0 20 7
B __str__() 0 15 7
A __setattr__() 0 9 2
A __getslice__() 0 3 1
B __getitem__() 0 19 5
A json() 0 4 1
A set_item_value() 0 10 1
A __delitem__() 0 16 4
B __setitem__() 0 27 2
A dictview() 0 8 2
A set_item() 0 21 4
A __getattr__() 0 15 2
B append() 0 16 6
A insert() 0 8 4

How to fix   Complexity   

Complex Class

Complex classes like SectionItems 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
import json
2
import logging
3
4
# The standard library OrderedDict was introduced in Python 2.7 so
5
# we have a third-party option to support Python 2.6
6
7
try:
8
    from collections import OrderedDict
9
except ImportError:
10
    from ordereddict import OrderedDict
11
12
import numpy as np
13
14
logger = logging.getLogger(__name__)
15
16
17
class HeaderItem(OrderedDict):
18
19
    '''Dictionary/namedtuple-style object for a LAS header line.
20
21
    Arguments:
22
        mnemonic (str): the mnemonic
23
        unit (str): the unit (no whitespace!)
24
        value (str): value
25
        descr (str): description
26
27
    These arguments are available for use as either items or attributes of the
28
    object.
29
30
    '''
31
    def __init__(self, mnemonic='', unit='', value='', descr=''):
32
        super(HeaderItem, self).__init__()
33
34
        # The original mnemonic needs to be stored for rewriting a new file.
35
        # it might be nothing - '' - or a duplicate e.g. two 'RHO' curves,
36
        # or unique - 'X11124' - or perhaps invalid??
37
38
        self.original_mnemonic = mnemonic
39
40
        # We also need to store a more useful mnemonic, which will be used
41
        # (technically not, but read on) for people to access the curve while
42
        # the LASFile object exists. For example, a curve which is unnamed
43
        # and has an original_mnemonic of '' will be accessed as 'UNKNOWN'.
44
45
        if mnemonic.strip() == '':
46
            self.useful_mnemonic = 'UNKNOWN'
47
        else:
48
            self.useful_mnemonic = mnemonic
49
50
        # But note that we need to (later) check (repeatedly) for duplicate
51
        # mnemonics. Any duplicates will have ':1', ':2', ':3', etc., appended
52
        # to them. The result of this will be stored as the
53
        # HeaderItem.mnemonic attribute through the below method.
54
55
        self.set_session_mnemonic_only(self.useful_mnemonic)
56
57
        self.unit = unit
58
        self.value = value
59
        self.descr = descr
60
61
    def set_session_mnemonic_only(self, value):
62
        '''Set the mnemonic for session use.
63
64
        See source comments for :class:`lasio.las_items.HeaderItem.__init__` 
65
        for a more in-depth explanation.
66
67
        '''
68
        super(HeaderItem, self).__setattr__('mnemonic', value)
69
70
    def __getitem__(self, key):
71
        '''Provide item dictionary-like access.'''
72
        if key == 'mnemonic':
73
            return self.mnemonic
74
        elif key == 'original_mnemonic':
75
            return self.original_mnemonic
76
        elif key == 'useful_mnemonic':
77
            return self.useful_mnemonic
78
        elif key == 'unit':
79
            return self.unit
80
        elif key == 'value':
81
            return self.value
82
        elif key == 'descr':
83
            return self.descr
84
        else:
85
            raise KeyError(
86
                'CurveItem only has restricted items (not %s)' % key)
87
88
    def __setattr__(self, key, value):
89
        
90
        # The user wants to rename the item! This means we must send their
91
        # new mnemonic to the original_mnemonic attribute. Remember that the
92
        # mnemonic attribute is for session use only.
93
94
        if key == 'mnemonic':
95
            super(HeaderItem, self).__setattr__('original_mnemonic', value)
96
97
        # Otherwise it's fine.
98
99
        super(HeaderItem, self).__setattr__(key, value)
100
101
    def __repr__(self):
102
        return (
103
            '%s(mnemonic=%s, unit=%s, value=%s, '
104
            'descr=%s, original_mnemonic=%s)' % (
105
                self.__class__.__name__, self.mnemonic, self.unit, self.value,
106
                self.descr, self.original_mnemonic))
107
108
    def _repr_pretty_(self, p, cycle):
109
        return p.text(self.__repr__())
110
111
    def __reduce__(self):
112
        return self.__class__, (self.mnemonic, self.unit, self.value, 
113
                                self.descr)
114
115
    @property
116
    def json(self):
117
        return json.dumps({
118
            '_type': self.__class__.__name__,
119
            'mnemonic': self.original_mnemonic,
120
            'unit': self.unit,
121
            'value': self.value,
122
            'descr': self.descr
123
            })
124
125
    @json.setter
126
    def json(self, value):
127
        raise Exception('Cannot set objects from JSON')
128
129
130
class CurveItem(HeaderItem):
131
132
    '''Dictionary/namedtuple-style object for a LAS curve.
133
134
    See :class:`lasio.las_items.HeaderItem`` for the (keyword) arguments.
135
136
    Keyword Arguments:
137
        data (array-like, 1-D): the curve's data.
138
139
    '''
140
141
    def __init__(self, *args, **kwargs):
142
        if "data" in kwargs:
143
            self.data = np.asarray(kwargs["data"])
144
            del kwargs["data"]
145
        else:
146
            self.data = np.ndarray([])
147
        super(CurveItem, self).__init__(*args, **kwargs)
148
149
    @property
150
    def API_code(self):
151
        '''Equivalent to the ``value`` attribute.'''
152
        return self.value
153
154
    def __repr__(self):
155
        return (
156
            '%s(mnemonic=%s, unit=%s, value=%s, '
157
            'descr=%s, original_mnemonic=%s, data.shape=%s)' % (
158
                self.__class__.__name__, self.mnemonic, self.unit, self.value,
159
                self.descr, self.original_mnemonic, self.data.shape))
160
161
    @property
162
    def json(self):
163
        return json.dumps({
164
            '_type': self.__class__.__name__,
165
            'mnemonic': self.original_mnemonic,
166
            'unit': self.unit,
167
            'value': self.value,
168
            'descr': self.descr,
169
            'data': list(self.data),
170
            })
171
172
    @json.setter
173
    def json(self, value):
174
        raise Exception('Cannot set objects from JSON')
175
176
177
class SectionItems(list):
178
179
    '''Variant of a ``list`` which is used to represent a LAS section.
180
181
    '''
182
183
    def __str__(self):
184
        rstr_lines = []
185
        data = [['Mnemonic', 'Unit', 'Value', 'Description'],
186
                ['--------', '----', '-----', '-----------']]
187
        data += [[str(x) for x in [item.mnemonic, item.unit, item.value, 
188
                                   item.descr]] for item in self]
189
        col_widths = []
190
        for i in range(len(data[0])):
191
            col_widths.append(max([len(row[i]) for row in data]))
192
        for row in data:
193
            line_items = []
194
            for i, item in enumerate(row):
195
                line_items.append(item.ljust(col_widths[i] + 2))
196
            rstr_lines.append(''.join(line_items))
197
        return '\n'.join(rstr_lines)
198
199
    def __contains__(self, testitem):
200
        '''Check whether a header item or mnemonic is in the section.
201
202
        Arguments:
203
            testitem (HeaderItem, CurveItem, str): either an item or a mnemonic
204
205
        Returns:
206
            bool
207
208
        '''
209
        for item in self:
210
            if testitem == item.mnemonic:
211
                return True
212
            elif hasattr(testitem, 'mnemonic'):
213
                if testitem.mnemonic == item.mnemonic:
214
                    return True
215
            elif testitem is item:
216
                return True
217
        else:
218
            return False
219
220
    def keys(self):
221
        '''Return mnemonics of all the HeaderItems in the section.'''
222
        return [item.mnemonic for item in self]
223
224
    def values(self):
225
        '''Return HeaderItems in the section.'''
226
        return self
227
228
    def items(self):
229
        '''Return pairs of (mnemonic, HeaderItem) from the section.'''
230
        return [(item.mnemonic, item) for item in self]
231
232
    def iterkeys(self):
233
        return iter(self.keys())
234
235
    def itervalues(self):
236
        return iter(self)
237
238
    def iteritems(self):
239
        return iter(self.items())
240
241
    def __getslice__(self, i0, i1):
242
        '''For Python 2.7 compatibility.'''
243
        return self.__getitem__(slice(i0, i1))
244
245
    def __getitem__(self, key):
246
        '''Item-style access by either mnemonic or index.
247
248
        Arguments:
249
            key (str, int, slice): either a mnemonic or the index to the list.
250
251
        Returns:
252
            item from the list (either HeaderItem or CurveItem)
253
254
        '''
255
        if isinstance(key, slice):
256
            return SectionItems(super(SectionItems, self).__getitem__(key))
257
        for item in self:
258
            if item.mnemonic == key:
259
                return item
260
        if isinstance(key, int):
261
            return super(SectionItems, self).__getitem__(key)
262
        else:
263
            raise KeyError('%s not in %s' % (key, self.keys()))
264
265
    def __delitem__(self, key):
266
        '''Delete item by either mnemonic or index.
267
268
        Arguments:
269
            key (str, int): either a mnemonic or the index to the list.
270
271
        '''
272
        for ix, item in enumerate(self):
273
            if item.mnemonic == key:
274
                super(SectionItems, self).__delitem__(ix)
275
                return
276
        if isinstance(key, int):
277
            super(SectionItems, self).__delitem__(key)
278
            return
279
        else:
280
            raise KeyError('%s not in %s' % (key, self.keys()))
281
282
    def __setitem__(self, key, newitem):
283
        '''Either replace the item or its value.
284
285
        Arguments:
286
            key (int, str): either the mnemonic or the index.
287
            newitem (HeaderItem or str/float/int): the thing to be set.
288
289
        If ``newitem`` is a :class:`lasio.las_items.HeaderItem` then the
290
        existing item will be replaced. Otherwise the existing item's ``value``
291
        attribute will be replaced.
292
293
        i.e. this allows us to do
294
295
            >>> section.OPERATOR
296
            HeaderItem(mnemonic='OPERATOR', value='John')
297
            >>> section.OPERATOR = 'Kent'
298
            >>> section.OPERATOR
299
            HeaderItem(mnemonic='OPERATOR', value='Kent')
300
301
        See :meth:`lasio.las_items.SectionItems.set_item` and 
302
        :meth:`lasio.las_items.SectionItems.set_item_value`.
303
304
        '''
305
        if isinstance(newitem, HeaderItem):
306
            self.set_item(key, newitem)
307
        else:
308
            self.set_item_value(key, newitem)
309
310
    def __getattr__(self, key):
311
        '''Provide attribute access via __contains__ e.g.
312
313
            >>> section['VERS']
314
            HeaderItem(mnemonic='VERS', ...)
315
            >>> 'VERS' in section
316
            True
317
            >>> section.VERS
318
            HeaderItem(mnemonic='VERS', ...)
319
320
        '''
321
        if key in self:
322
            return self[key]
323
        else:
324
            super(SectionItems, self).__getattr__(key)
325
326
    def __setattr__(self, key, value):
327
        '''Allow access to :meth:`lasio.las_items.SectionItems.__setitem__`
328
        via attribute access.
329
330
        '''
331
        if key in self:
332
            self[key] = value
333
        else:
334
            super(SectionItems, self).__setattr__(key, value)
335
336
    def set_item(self, key, newitem):
337
        '''Replace an item by comparison of session mnemonics.
338
339
        Arguments:
340
            key (str): the item mnemonic (or HeaderItem with mnemonic) 
341
                you want to replace.
342
            newitem (HeaderItem): the new item
343
344
        If **key** is not present, it appends **newitem**.
345
346
        '''
347
        for i, item in enumerate(self):
348
            if key == item.mnemonic:
349
350
                # This is very important. We replace items where
351
                # 'mnemonic' is equal - i.e. we do not check 
352
                # against original_mnemonic.
353
354
                return super(SectionItems, self).__setitem__(i, newitem)
355
        else:
356
            self.append(newitem)
357
358
    def set_item_value(self, key, value):
359
        '''Set the ``value`` attribute of an item.
360
361
        Arguments:
362
            key (str): the mnemonic of the item (or HeaderItem with the
363
                mnemonic) you want to edit
364
            value (str, int, float): the new value.
365
366
        '''
367
        self[key].value = value
368
369
    def append(self, newitem):
370
        '''Append a new HeaderItem to the object.'''
371
        super(SectionItems, self).append(newitem)
372
373
        # Check to fix the :n suffixes
374
        existing = [item.useful_mnemonic for item in self]
375
        locations = []
376
        for i, item in enumerate(self):
377
            if item.useful_mnemonic == newitem.mnemonic:
378
                locations.append(i)
379
        if len(locations) > 1:
380
            current_count = 1
381
            for i, loc in enumerate(locations):
382
                item = self[loc]
383
                item.set_session_mnemonic_only(item.useful_mnemonic + ':%d'
384
                                               % (i + 1))
385
386
    def insert(self, i, newitem):
387
        '''Insert a new HeaderItem to the object.'''        
388
        existing = [j for j in range(len(self)) 
389
                    if self[j].useful_mnemonic == newitem.useful_mnemonic]
390
        super(SectionItems, self).insert(i, newitem)
391
        if len(existing):
392
            newitem.set_session_mnemonic_only(
393
                newitem.useful_mnemonic + ':{:d}'.format(len(existing) + 1))
394
        
395
    def dictview(self):
396
        '''View of mnemonics and values as a dict.
397
398
        Returns:
399
            dict - keys are the mnemonics and the values are the ``value``
400
            attributes.
401
        '''
402
        return dict(zip(self.keys(), [i.value for i in self.values()]))
403
404
    @property
405
    def json(self):
406
        return json.dumps(
407
            [item.json for item in self.values()])
408
409
    @json.setter
410
    def json(self, value):
411
        raise Exception('Cannot set objects from JSON')