clitool 0.4.1 documentation

clitool.textio

Contents

Source code for clitool.textio

#!/usr/bin/env python
# -*- coding: utf-8 -*-

"""Text I/O utilities.

Type mapping follows the rule of "`Cerberus
<http://cerberus.readthedocs.org/en/latest/>`_".

Example usage::

    import logging
    import sys

    FIELDS = (
        {'id': 'id', 'type': 'string'},
        {'id': 'updated', 'type': 'datetime', 'format': '%Y-%m-%dT%H:%M:%SZ'},
        {'id': 'name', 'type': 'string'},
        {'id': 'latitude', 'type': 'float'},
        {'id': 'longitude', 'type': 'float'},
        {'id': 'zipcode', 'type': 'string'},
        {'id': 'kind', 'type': 'string', 'default': 'UNKNOWN'},
        {'id': 'update_type', 'type': 'integer'}
    )

    s = Sequential()
    s.callback(RowMapper(FIELDS))
    s.errback(logging.error)
    for row in sys.stdin:
        dt = s(row)
        print(dt)

"""

import sys
from datetime import datetime


[docs]class Sequential(object): """Apply callback functions sequentially. If error occurs in callback, apply errback functions against catched exception. :rtype: callable """ callbacks = [] errbacks = [] def callback(self, callback): self.callbacks.append(callback) def errback(self, errback): self.errbacks.append(errback) def __call__(self, value): try: for callback in self.callbacks: value = callback(value) if value is None: return except Exception: e = sys.exc_info()[1] for errback in self.errbacks: errback(e) return return value
[docs]class RowMapper(object): """ Map `list_or_tuple` to dict object using given fields definition. If length of given data is not different from keys length, raise ``ValueError``. To accept loose inputs, change ``strict`` flag ``False``. If input fields are all string type, use `namedtuple <http://docs.python.org/2/library/collections.html>`_ in standard library instead. The case, however, that keys contains non-ascii characters, such as Japanese text, this class may be useful. :param fields: list of fields values. :type fields: tuple :param strict: strict match flag. :type strict: boolean :rtype: callable """ def __init__(self, fields, strict=True, *args, **kwargs): self.fields = fields self.strict = strict def __call__(self, row, *args, **kwargs): """ :param row: tuple value such as each row of csv file. :type row: tuple or list :rtype: dictionary after binding with fields values. """ if not row: return if len(self.fields) != len(row) and self.strict: raise ValueError('Size differ: expected={}, actual={}'.format( len(self.fields), len(row))) dt = {} for h, v in zip(self.fields, row): if not v: continue k, t = h['id'], h['type'] if t == 'string': dt[k] = v elif t == 'integer': dt[k] = int(v) elif t == 'float': dt[k] = float(v) elif t == 'boolean': dt[k] = bool(v) elif t == 'datetime': dt[k] = datetime.strptime(v, h['format']) else: raise ValueError('Unknown type "{}" for "{}"'.format(t, k)) return dt
[docs]class DictMapper(object): """Convert dictionary object to list of strings of values. """ def __init__(self, fields): self.fields = fields def __call__(self, dt): out = [] for f in self.fields: k, t = f['id'], f['type'] v = dt.get(k, f.get('default', '')) if t == 'string': val = v elif not v: val = '' elif t == 'datetime': val = v.strftime(f['format']) elif t == 'integer': val = str(v) elif t == 'float': if 'precision' in f: v = round(v, f['precision']) val = str(v) elif t == 'boolean': m = f.get('mapping', {}) if v in m: val = m[v] else: val = str(v) else: raise ValueError('Unknown type "{}" for "{}"'.format(t, k)) out.append(val) return out # vim: set et ts=4 sw=4 cindent fileencoding=utf-8 :

Contents