Passed
Push — master ( cdc1b3...61816f )
by Kent
01:23 queued 41s
created

HeaderItem.useful_mnemonic()   A

Complexity

Conditions 1

Size

Total Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

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