SectionItems.__setitem__()   A
last analyzed

Complexity

Conditions 2

Size

Total Lines 27

Duplication

Lines 0
Ratio 0 %

Importance

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