Completed
Push — master ( afd9d8...0f19a6 )
by Kent
16s
created

SectionItems.mnemonic_compare()   B

Complexity

Conditions 5

Size

Total Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

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