Completed
Push — master ( 525852...81f879 )
by Kent
10s
created

SectionItems.assign_duplicate_suffixes()   D

Complexity

Conditions 9

Size

Total Lines 23

Duplication

Lines 0
Ratio 0 %

Importance

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