Permalink
Browse files

Added validating writer (Ticket #45).

  • Loading branch information...
1 parent 306b137 commit 6d0c3ff5b51fabcf8dbb9884d0fb6f8d67d4ebcf @roskakori committed Apr 13, 2012
Showing with 112 additions and 11 deletions.
  1. +7 −5 cutplace/_parsers.py
  2. +87 −6 cutplace/interface.py
  3. +9 −0 cutplace/test_interface.py
  4. +9 −0 docs/changes.rst
View
@@ -240,13 +240,15 @@ def asCsvDialect(self):
"""
Represent dialect as csv.Dialect.
"""
- result = csv.Dialect()
+ result = csv.excel()
result.lineterminator = self.lineDelimiter
- result.delimiter = self.itemDelimiter
- result.quotechar = self.quoteChar
+ result.delimiter = str(self.itemDelimiter)
+ result.quotechar = str(self.quoteChar)
result.doublequote = (self.escapeChar == self.quoteChar)
- if not result.doublequote:
- result.escapechar = self.escapeChar
+ if self.escapeChar is None:
+ result.escapechar = None
+ else:
+ result.escapechar = str(self.escapeChar)
result.skipinitialspace = (self.blanksAroundItemDelimiter)
return result
View
@@ -1,7 +1,7 @@
"""
Interface control document (ICD) describing all aspects of a data driven interface.
"""
-# Copyright (C) 2009-2011 Thomas Aglassinger
+# Copyright (C) 2009-2012 Thomas Aglassinger
#
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by
@@ -501,11 +501,7 @@ def _rejectRow(self, row, reason, location):
def _reader(self, dataFile):
if self.dataFormat.name in (data.FORMAT_CSV, data.FORMAT_DELIMITED):
- dialect = _parsers.DelimitedDialect()
- dialect.lineDelimiter = self.dataFormat.get(data.KEY_LINE_DELIMITER)
- dialect.itemDelimiter = self.dataFormat.get(data.KEY_ITEM_DELIMITER)
- dialect.quoteChar = self.dataFormat.get(data.KEY_QUOTE_CHARACTER)
- # FIXME: Set escape char according to ICD.
+ dialect = _createDelimitedDialect(self)
reader = _parsers.delimitedReader(dataFile, dialect, encoding=self.dataFormat.encoding)
elif self.dataFormat.name == data.FORMAT_EXCEL:
sheet = self.dataFormat.get(data.KEY_SHEET)
@@ -852,6 +848,80 @@ def removeValidationListener(self, listener):
doc="If ``True``, log stack trace on rejected data items or rows.")
+class Writer(object):
+ """
+ Writer to write data to a filelike output while validating that they conform to an
+ `InterfaceControlDocument`.
+ """
+ def __init__(self, icd, out):
+ assert icd is not None
+ assert out is not None
+
+ dataFormatName = icd.dataFormat.name
+ self._icd = icd
+ self._out = out
+ if dataFormatName in (data.FORMAT_CSV, data.FORMAT_DELIMITED):
+ csvDialect = _createDelimitedDialect(self._icd).asCsvDialect()
+ self._writer = _tools.UnicodeCsvWriter(self._out, dialect=csvDialect)
+ else:
+ raise NotImplementedError("format=%r" % dataFormatName)
+ assert self._writer is not None
+
+ # Write header.
+ headerRowIndex = self._icd.dataFormat.get(data.KEY_HEADER)
+ assert headerRowIndex is not None
+ assert headerRowIndex >= 0
+ if headerRowIndex > 0:
+ for _ in xrange(headerRowIndex):
+ self._writeRow([])
+ self._writeRow(self._icd.fieldNames)
+
+ def __enter__(self):
+ return self
+
+ def __exit__(self, errorType, error, traceback):
+ if not error:
+ # There's no point in calling `close()` in case of previous errors
+ # because it most likely will cause another error and thus discard
+ # the original error which holds actually useful information.
+ self.close()
+
+ @property
+ def out(self):
+ """
+ The filelike output to which data are written.
+ """
+ return self._out
+
+ def _writeRow(self, row):
+ """
+ Write ``row`` without any validation. This is useful to write header rows.
+ """
+ assert row is not None
+ # TODO: Keep track of location.
+ self._writer.writerow(row)
+
+ def writeRow(self, row):
+ """
+ Write ``row`` to ``out``.
+ """
+ assert row is not None
+ # FIXME: Validate row.
+ self._writeRow(row)
+
+ def writeRows(self, rows):
+ """
+ Write all ``rows`` to ``out``.
+ """
+ assert rows is not None
+ for row in rows:
+ self.writeRow(row)
+
+ def close(self):
+ # TODO: Validate checks at end.
+ pass
+
+
def createSniffedInterfaceControlDocument(readable, **keywords):
"""
`InterfaceControlDocument` created by examining the contents of ``readable``.
@@ -893,6 +963,17 @@ def validatedRows(icd, dataFileToValidatePath, errors="strict"):
return icd.validatedRows(dataFileToValidatePath, errors)
+def _createDelimitedDialect(icd):
+ assert icd is not None
+ assert icd.dataFormat.name in (data.FORMAT_CSV, data.FORMAT_DELIMITED), "icd.dataFormat=%r" % icd.dataFormat.name
+ result = _parsers.DelimitedDialect()
+ result.lineDelimiter = icd.dataFormat.get(data.KEY_LINE_DELIMITER)
+ result.itemDelimiter = icd.dataFormat.get(data.KEY_ITEM_DELIMITER)
+ result.quoteChar = icd.dataFormat.get(data.KEY_QUOTE_CHARACTER)
+ # FIXME: Set escape char according to ICD.
+ return result
+
+
def importPlugins(folderToScanPath):
"""
Import all Python modules found in folder ``folderToScanPath`` (non recursively) consequently
@@ -20,6 +20,7 @@
import StringIO
import unittest
import xlrd
+from contextlib import closing
import checks
import data
@@ -973,6 +974,14 @@ def testCanValidateFieldFormatFromPlugin(self):
icd.validate(dataReadable)
+class WriterTest(unittest.TestCase):
+ def testCanWriteValidDataToCsv(self):
+ icd = createDefaultTestIcd(data.FORMAT_CSV)
+ with closing(StringIO.StringIO()) as out:
+ csvWriter = interface.Writer(icd, out)
+ csvWriter.writeRow([u"38123", u"12345", u"John", u"Doe", u"male", u"08.03.1957"])
+ self.assertEqual(out.getvalue(), u"38123,12345,John,Doe,male,08.03.1957\n")
+
if __name__ == '__main__': # pragma: no cover
logging.basicConfig()
logging.getLogger("cutplace").setLevel(logging.INFO)
View
@@ -4,6 +4,15 @@ Revision history
This chapter describes improvements compared to earlier versions of cutplace.
+Version 0.7.1, 2012-04-xx
+=========================
+
+* Added validating writer, see ``interface.Writer`` for more information
+ (Ticket #45).
+
+* Cleaned up build and the section on :ref:`jenkins` so that everything works
+ as described even if Jenkins runs as deamon with MacPorts.
+
Version 0.7.0, 2012-01-09
=========================

0 comments on commit 6d0c3ff

Please sign in to comment.