Passed
Push — master ( ccfa4f...7f3a96 )
by Kent
58s
created

SectionItems.__delitem__()   A

Complexity

Conditions 4

Size

Total Lines 16

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 1 Features 0
Metric Value
cc 4
c 1
b 1
f 0
dl 0
loc 16
rs 9.2
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 __getitem__(self, key):
242
        '''Item-style access by either mnemonic or index.
243
244
        Arguments:
245
            key (str, int): either a mnemonic or the index to the list.
246
247
        Returns:
248
            item from the list (either HeaderItem or CurveItem)
249
250
        '''
251
        for item in self:
252
            if item.mnemonic == key:
253
                return item
254
        if isinstance(key, int):
255
            return super(SectionItems, self).__getitem__(key)
256
        else:
257
            raise KeyError('%s not in %s' % (key, self.keys()))
258
259
    def __delitem__(self, key):
260
        '''Delete item by either mnemonic or index.
261
262
        Arguments:
263
            key (str, int): either a mnemonic or the index to the list.
264
265
        '''
266
        for ix, item in enumerate(self):
267
            if item.mnemonic == key:
268
                super(SectionItems, self).__delitem__(ix)
269
                return
270
        if isinstance(key, int):
271
            super(SectionItems, self).__delitem__(key)
272
            return
273
        else:
274
            raise KeyError('%s not in %s' % (key, self.keys()))
275
276
    def __setitem__(self, key, newitem):
277
        '''Either replace the item or its value.
278
279
        Arguments:
280
            key (int, str): either the mnemonic or the index.
281
            newitem (HeaderItem or str/float/int): the thing to be set.
282
283
        If ``newitem`` is a :class:`lasio.las_items.HeaderItem` then the
284
        existing item will be replaced. Otherwise the existing item's ``value``
285
        attribute will be replaced.
286
287
        i.e. this allows us to do
288
289
            >>> section.OPERATOR
290
            HeaderItem(mnemonic='OPERATOR', value='John')
291
            >>> section.OPERATOR = 'Kent'
292
            >>> section.OPERATOR
293
            HeaderItem(mnemonic='OPERATOR', value='Kent')
294
295
        See :meth:`lasio.las_items.SectionItems.set_item` and 
296
        :meth:`lasio.las_items.SectionItems.set_item_value`.
297
298
        '''
299
        if isinstance(newitem, HeaderItem):
300
            self.set_item(key, newitem)
301
        else:
302
            self.set_item_value(key, newitem)
303
304
    def __getattr__(self, key):
305
        '''Provide attribute access via __contains__ e.g.
306
307
            >>> section['VERS']
308
            HeaderItem(mnemonic='VERS', ...)
309
            >>> 'VERS' in section
310
            True
311
            >>> section.VERS
312
            HeaderItem(mnemonic='VERS', ...)
313
314
        '''
315
        if key in self:
316
            return self[key]
317
        else:
318
            super(SectionItems, self).__getattr__(key)
319
320
    def __setattr__(self, key, value):
321
        '''Allow access to :meth:`lasio.las_items.SectionItems.__setitem__`
322
        via attribute access.
323
324
        '''
325
        if key in self:
326
            self[key] = value
327
        else:
328
            super(SectionItems, self).__setattr__(key, value)
329
330
    def set_item(self, key, newitem):
331
        '''Replace an item by comparison of session mnemonics.
332
333
        Arguments:
334
            key (str): the item mnemonic (or HeaderItem with mnemonic) 
335
                you want to replace.
336
            newitem (HeaderItem): the new item
337
338
        If **key** is not present, it appends **newitem**.
339
340
        '''
341
        for i, item in enumerate(self):
342
            if key == item.mnemonic:
343
344
                # This is very important. We replace items where
345
                # 'mnemonic' is equal - i.e. we do not check 
346
                # against original_mnemonic.
347
348
                return super(SectionItems, self).__setitem__(i, newitem)
349
        else:
350
            self.append(newitem)
351
352
    def set_item_value(self, key, value):
353
        '''Set the ``value`` attribute of an item.
354
355
        Arguments:
356
            key (str): the mnemonic of the item (or HeaderItem with the
357
                mnemonic) you want to edit
358
            value (str, int, float): the new value.
359
360
        '''
361
        self[key].value = value
362
363
    def append(self, newitem):
364
        '''Append a new HeaderItem to the object.'''
365
        super(SectionItems, self).append(newitem)
366
367
        # Check to fix the :n suffixes
368
        existing = [item.useful_mnemonic for item in self]
369
        locations = []
370
        for i, item in enumerate(self):
371
            if item.useful_mnemonic == newitem.mnemonic:
372
                locations.append(i)
373
        if len(locations) > 1:
374
            current_count = 1
375
            for i, loc in enumerate(locations):
376
                item = self[loc]
377
                item.set_session_mnemonic_only(item.useful_mnemonic + ':%d'
378
                                               % (i + 1))
379
380
    def insert(self, i, newitem):
381
        '''Insert a new HeaderItem to the object.'''        
382
        existing = [j for j in range(len(self)) 
383
                    if self[j].useful_mnemonic == newitem.useful_mnemonic]
384
        super(SectionItems, self).insert(i, newitem)
385
        if len(existing):
386
            newitem.set_session_mnemonic_only(
387
                newitem.useful_mnemonic + ':{:d}'.format(len(existing) + 1))
388
        
389
    def dictview(self):
390
        '''View of mnemonics and values as a dict.
391
392
        Returns:
393
            dict - keys are the mnemonics and the values are the ``value``
394
            attributes.
395
        '''
396
        return dict(zip(self.keys(), [i.value for i in self.values()]))
397
398
    @property
399
    def json(self):
400
        return json.dumps(
401
            [item.json for item in self.values()])
402
403
    @json.setter
404
    def json(self, value):
405
        raise Exception('Cannot set objects from JSON')