summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKenneth Reitz <me@kennethreitz.com>2010-11-17 21:00:56 -0500
committerKenneth Reitz <me@kennethreitz.com>2010-11-17 21:00:56 -0500
commit7727171379683dcf859a344d6c8a43dc6d5cfa2b (patch)
tree5a38c420e641e11097017e0f08c907b1f2875a3d
parent09d057094e70b582878efab3858ec5b7f485e215 (diff)
parent91bd4eb9c736270ab5b5eb88f7ebaa790c0ddcfc (diff)
downloadtablib-7727171379683dcf859a344d6c8a43dc6d5cfa2b.tar.gz
Merge branch 'release/0.9.2'v0.9.2
-rw-r--r--HISTORY.rst9
-rw-r--r--NOTICE29
-rw-r--r--README.rst5
-rw-r--r--TODO.rst11
-rw-r--r--docs/tutorial.rst6
-rw-r--r--setup.py2
-rw-r--r--tablib/core.py236
-rw-r--r--tablib/formats/_xls.py9
-rw-r--r--tablib/packages/ordereddict.py127
-rwxr-xr-xtest_tablib.py75
10 files changed, 411 insertions, 98 deletions
diff --git a/HISTORY.rst b/HISTORY.rst
index d467712..c64d7b5 100644
--- a/HISTORY.rst
+++ b/HISTORY.rst
@@ -1,6 +1,15 @@
History
-------
+0.9.2 (2010-11-17)
+++++++++++++++++++
+
+* Tanspose method added to Datasets
+* New frozen top row in Excel output
+* Pickling support for Datasets and Rows
+* Support for row/column stacking
+
+
0.9.1 (2010-11-04)
++++++++++++++++++
diff --git a/NOTICE b/NOTICE
index f072112..88d5d2d 100644
--- a/NOTICE
+++ b/NOTICE
@@ -1,4 +1,31 @@
-Tablib includes some vendorized python libraries: pyyaml, simplejson, and xlwt.
+Tablib includes some vendorized python libraries: ordereddict, pyyaml,
+simplejson, and xlwt.
+
+
+OrderedDict License
+===================
+
+Copyright (c) 2009 Raymond Hettinger
+
+Permission is hereby granted, free of charge, to any person
+obtaining a copy of this software and associated documentation files
+(the "Software"), to deal in the Software without restriction,
+including without limitation the rights to use, copy, modify, merge,
+publish, distribute, sublicense, and/or sell copies of the Software,
+and to permit persons to whom the Software is furnished to do so,
+subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be
+ included in all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+ OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ OTHER DEALINGS IN THE SOFTWARE.
diff --git a/README.rst b/README.rst
index 3d01ed8..00b6345 100644
--- a/README.rst
+++ b/README.rst
@@ -61,7 +61,7 @@ Intelligently add new rows: ::
Intelligently add new columns: ::
- >>> data.append(col=('age', 90, 67, 83))
+ >>> data.append(col=(90, 67, 83), header='age')
Slice rows: ::
@@ -171,8 +171,7 @@ To install tablib, simply: ::
Or, if you absolutely must: ::
$ easy_install tablib
-
-
+
Contribute
----------
diff --git a/TODO.rst b/TODO.rst
index c28c8b7..231f03e 100644
--- a/TODO.rst
+++ b/TODO.rst
@@ -1,9 +1,8 @@
-* Polish *&* announce http://tablib.org.
+* Backwards-compatible OrderedDict support
* Write more exhausive unit-tests.
* Write stress tests.
* Make CSV write customizable.
-* HTML Table exports.
-* ``Dataset.traspose()`` support?
-
-
-
+* HTML Table exports.
+* Integrate django-tablib
+* Mention django-tablib in Documention
+* Dataset title usage in documentation (#17) \ No newline at end of file
diff --git a/docs/tutorial.rst b/docs/tutorial.rst
index 774158d..dfe10ff 100644
--- a/docs/tutorial.rst
+++ b/docs/tutorial.rst
@@ -87,7 +87,7 @@ Adding Columns
Now that we have a basic :class:`Dataset` in place, let's add a column of **ages** to it. ::
- data.append(col=['Age', 22, 20])
+ data.append(col=[22, 20], header='Age')
Let's view the data now. ::
@@ -243,7 +243,7 @@ Filtering Datasets with Tags
.. versionadded:: 0.9.0
-When constructing a :class:`Dataset` object, you can add tags to rows by speficying the ``tags`` parameter.
+When constructing a :class:`Dataset` object, you can add tags to rows by specifying the ``tags`` parameter.
This allows you to filter your :class:`Dataset` later. This can be useful so seperate rows of data based on
arbitrary criteria (*e.g.* origin) that you don't want to include in your :class:`Dataset`.
@@ -350,4 +350,4 @@ The resulting **tests.xls** will have the following layout:
----
-Now, go check out the :ref:`API Documentation <api>` or begin :ref:`Tablib Development <development>`. \ No newline at end of file
+Now, go check out the :ref:`API Documentation <api>` or begin :ref:`Tablib Development <development>`.
diff --git a/setup.py b/setup.py
index 577e4d0..e779457 100644
--- a/setup.py
+++ b/setup.py
@@ -19,7 +19,7 @@ required = []
setup(
name='tablib',
- version='0.9.1',
+ version='0.9.2',
description='Format agnostic tabular data library (XLS, JSON, YAML, CSV)',
long_description=open('README.rst').read() + '\n\n' +
open('HISTORY.rst').read(),
diff --git a/tablib/core.py b/tablib/core.py
index b5c2526..bd2d4ba 100644
--- a/tablib/core.py
+++ b/tablib/core.py
@@ -15,12 +15,13 @@ from tablib import formats
__title__ = 'tablib'
-__version__ = '0.9.1'
-__build__ = 0x000901
+__version__ = '0.9.2'
+__build__ = 0x000902
__author__ = 'Kenneth Reitz'
__license__ = 'MIT'
__copyright__ = 'Copyright 2010 Kenneth Reitz'
+
class Row(object):
"""Internal Row object. Mainly used for filtering."""
@@ -51,6 +52,17 @@ class Row(object):
def __delitem__(self, i):
del self._row[i]
+ def __getstate__(self):
+ result = dict()
+ result['_row'] = self._row
+ result['tags'] = self.tags
+
+ return result
+
+ def __setstate__(self, state):
+ self._row = state['_row']
+ self.tags = state['tags']
+
def append(self, value):
self._row.append(value)
@@ -76,44 +88,41 @@ class Row(object):
if tag == None:
return False
elif isinstance(tag, basestring):
- return tag in self.tags
+ return (tag in self.tags)
else:
- for t in tag:
- if t in self.tags:
- return True
- return False
+ return True if len(set(tag) & set(self.tags)) else False
class Dataset(object):
- """The :class:`Dataset` object is the heart of Tablib. It provides all core
+ """The :class:`Dataset` object is the heart of Tablib. It provides all core
functionality.
-
+
Usually you create a :class:`Dataset` instance in your main module, and append
rows and columns as you collect data. ::
-
+
data = tablib.Dataset()
data.headers = ('name', 'age')
-
+
for (name, age) in some_collector():
data.append((name, age))
-
+
You can also set rows and headers upon instantiation. This is useful if dealing
with dozens or hundres of :class:`Dataset` objects. ::
-
+
headers = ('first_name', 'last_name')
data = [('John', 'Adams'), ('George', 'Washington')]
-
+
data = tablib.Dataset(*data, headers=headers)
-
-
+
+
:param \*args: (optional) list of rows to populate Dataset
:param headers: (optional) list strings for Dataset header row
.. admonition:: Format Attributes Definition
- If you look at the code, the various output/import formats are not
- defined within the :class:`Dataset` object. To add support for a new format, see
+ If you look at the code, the various output/import formats are not
+ defined within the :class:`Dataset` object. To add support for a new format, see
:ref:`Adding New Formats <newformats>`.
"""
@@ -121,7 +130,7 @@ class Dataset(object):
def __init__(self, *args, **kwargs):
self._data = list(Row(arg) for arg in args)
self.__headers = None
-
+
# ('title', index) tuples
self._separators = []
@@ -137,7 +146,7 @@ class Dataset(object):
self._register_formats()
-
+
def __len__(self):
return self.height
@@ -156,7 +165,7 @@ class Dataset(object):
else:
return [result.tuple for result in _results]
-
+
def __setitem__(self, key, value):
self._validate(value)
self._data[key] = Row(value)
@@ -166,10 +175,10 @@ class Dataset(object):
if isinstance(key, basestring):
if key in self.headers:
-
+
pos = self.headers.index(key)
del self.headers[pos]
-
+
for i, row in enumerate(self._data):
del row[pos]
@@ -186,7 +195,7 @@ class Dataset(object):
except AttributeError:
return '<dataset object>'
-
+
@classmethod
def _register_formats(cls):
"""Adds format properties."""
@@ -196,7 +205,7 @@ class Dataset(object):
setattr(cls, fmt.title, property(fmt.export_set, fmt.import_set))
except AttributeError:
setattr(cls, fmt.title, property(fmt.export_set))
-
+
except AttributeError:
pass
@@ -237,21 +246,21 @@ class Dataset(object):
def _clean_col(self, col):
"""Prepares the given column for insert/append."""
-
+
col = list(col)
-
+
if self.headers:
header = [col.pop(0)]
else:
header = []
-
+
if len(col) == 1 and callable(col[0]):
col = map(col[0], self._data)
col = tuple(header + col)
-
+
return col
-
+
@property
def height(self):
"""The number of rows currently in the :class:`Dataset`.
@@ -265,7 +274,7 @@ class Dataset(object):
"""The number of columns currently in the :class:`Dataset`.
Cannot be directly modified.
"""
-
+
try:
return len(self._data[0])
except IndexError:
@@ -278,7 +287,7 @@ class Dataset(object):
@property
def headers(self):
"""An *optional* list of strings to be used for header rows and attribute names.
-
+
This must be set manually. The given list length must equal :class:`Dataset.width`.
"""
@@ -300,9 +309,9 @@ class Dataset(object):
@property
def dict(self):
- """A JSON representation of the :class:`Dataset` object. If headers have been
- set, a JSON list of objects will be returned. If no headers have
- been set, a JSON list of lists (rows) will be returned instead.
+ """A JSON representation of the :class:`Dataset` object. If headers have been
+ set, a JSON list of objects will be returned. If no headers have
+ been set, a JSON list of lists (rows) will be returned instead.
A dataset object can also be imported by setting the `Dataset.json` attribute: ::
@@ -312,10 +321,10 @@ class Dataset(object):
"""
return self._package()
-
+
@dict.setter
def dict(self, pickle):
- """A native Python representation of the Dataset object. If headers have been
+ """A native Python representation of the Dataset object. If headers have been
set, a list of Python dictionaries will be returned. If no headers have been
set, a list of tuples (rows) will be returned instead.
@@ -323,7 +332,7 @@ class Dataset(object):
data = tablib.Dataset()
data.dict = [{'age': 90, 'first_name': 'Kenneth', 'last_name': 'Reitz'}]
-
+
"""
if not len(pickle):
return
@@ -333,7 +342,7 @@ class Dataset(object):
self.wipe()
for row in pickle:
self.append(Row(row))
-
+
# if list of objects
elif isinstance(pickle[0], dict):
self.wipe()
@@ -356,11 +365,11 @@ class Dataset(object):
"""
pass
-
+
@property
def csv():
- """A CSV representation of the :class:`Dataset` object. The top row will contain
- headers, if they have been set. Otherwise, the top row will contain
+ """A CSV representation of the :class:`Dataset` object. The top row will contain
+ headers, if they have been set. Otherwise, the top row will contain
the first row of the dataset.
A dataset object can also be imported by setting the :class:`Dataset.csv` attribute. ::
@@ -374,8 +383,8 @@ class Dataset(object):
@property
def tsv():
- """A TSV representation of the :class:`Dataset` object. The top row will contain
- headers, if they have been set. Otherwise, the top row will contain
+ """A TSV representation of the :class:`Dataset` object. The top row will contain
+ headers, if they have been set. Otherwise, the top row will contain
the first row of the dataset.
A dataset object can also be imported by setting the :class:`Dataset.tsv` attribute. ::
@@ -388,9 +397,9 @@ class Dataset(object):
@property
def yaml():
- """A YAML representation of the :class:`Dataset` object. If headers have been
- set, a YAML list of objects will be returned. If no headers have
- been set, a YAML list of lists (rows) will be returned instead.
+ """A YAML representation of the :class:`Dataset` object. If headers have been
+ set, a YAML list of objects will be returned. If no headers have
+ been set, a YAML list of lists (rows) will be returned instead.
A dataset object can also be imported by setting the :class:`Dataset.json` attribute: ::
@@ -401,12 +410,12 @@ class Dataset(object):
"""
pass
-
+
@property
def json():
- """A JSON representation of the :class:`Dataset` object. If headers have been
- set, a JSON list of objects will be returned. If no headers have
- been set, a JSON list of lists (rows) will be returned instead.
+ """A JSON representation of the :class:`Dataset` object. If headers have been
+ set, a JSON list of objects will be returned. If no headers have
+ been set, a JSON list of lists (rows) will be returned instead.
A dataset object can also be imported by setting the :class:`Dataset.json` attribute: ::
@@ -447,18 +456,18 @@ class Dataset(object):
def insert(self, index, row=None, col=None, header=None, tags=list()):
- """Inserts a row or column to the :class:`Dataset` at the given index.
-
- Rows and columns inserted must be the correct size (height or width).
-
+ """Inserts a row or column to the :class:`Dataset` at the given index.
+
+ Rows and columns inserted must be the correct size (height or width).
+
The default behaviour is to insert the given row to the :class:`Dataset`
object at the given index. If the ``col`` parameter is given, however,
a new column will be insert to the :class:`Dataset` object instead.
You can also insert a column of a single callable object, which will
- add a new column with the return values of the callable each as an
+ add a new column with the return values of the callable each as an
item in the column. ::
-
+
data.append(col=random.randint)
See :ref:`dyncols` for an in-depth example.
@@ -472,7 +481,7 @@ class Dataset(object):
If inserting a row, you can add :ref:`tags <tags>` to the row you are inserting.
This gives you the ability to :class:`filter <Dataset.filter>` your
:class:`Dataset` later.
-
+
"""
if row:
self._validate(row)
@@ -480,7 +489,7 @@ class Dataset(object):
elif col:
col = list(col)
- # Callable Columns...
+ # Callable Columns...
if len(col) == 1 and callable(col[0]):
col = map(col[0], self._data)
@@ -492,7 +501,7 @@ class Dataset(object):
if not header:
raise HeadersNeeded()
self.headers.insert(index, header)
-
+
if self.height and self.width:
for i, row in enumerate(self._data):
@@ -504,13 +513,98 @@ class Dataset(object):
def filter(self, tag):
"""Returns a new instance of the :class:`Dataset`, excluding any rows
- that do not contain the given :ref:`tags <tags>`.
+ that do not contain the given :ref:`tags <tags>`.
"""
_dset = copy(self)
_dset._data = [row for row in _dset._data if row.has_tag(tag)]
-
+
return _dset
-
+
+ def transpose(self):
+ """Transpose a :class:`Dataset`, turning rows into columns and vice
+ versa, returning a new ``Dataset`` instance. The first row of the
+ original instance becomes the new header row."""
+
+ # Don't transpose if there is no data
+ if not self:
+ return
+
+ _dset = Dataset()
+ # The first element of the headers stays in the headers,
+ # it is our "hinge" on which we rotate the data
+ new_headers = [self.headers[0]] + self[self.headers[0]]
+
+ _dset.headers = new_headers
+ for column in self.headers:
+
+ if column == self.headers[0]:
+ # It's in the headers, so skip it
+ continue
+
+ # Adding the column name as now they're a regular column
+ row_data = [column] + self[column]
+ row_data = Row(row_data)
+ _dset.append(row=row_data)
+
+ return _dset
+
+
+ def stack_rows(self, other):
+ """Stack two :class:`Dataset` instances together by
+ joining at the row level, and return new combined
+ ``Dataset`` instance."""
+
+ if not isinstance(other, Dataset):
+ return
+
+ if self.width != other.width:
+ raise InvalidDimensions
+
+ # Copy the source data
+ _dset = copy(self)
+
+ rows_to_stack = [row for row in _dset._data]
+ other_rows = [row for row in other._data]
+
+ rows_to_stack.extend(other_rows)
+ _dset._data = rows_to_stack
+
+ return _dset
+
+
+ def stack_columns(self, other):
+ """Stack two :class:`Dataset` instances together by
+ joining at the column level, and return a new
+ combined ``Dataset`` instance. If either ``Dataset``
+ has headers set, than the other must as well."""
+
+ if not isinstance(other, Dataset):
+ return
+
+ if self.headers or other.headers:
+ if not self.headers or not other.headers:
+ raise HeadersNeeded
+
+ if self.height != other.height:
+ raise InvalidDimensions
+
+ try:
+ new_headers = self.headers + other.headers
+ except TypeError:
+ new_headers = None
+
+ _dset = Dataset()
+
+ for column in self.headers:
+ _dset.append(col=self[column])
+
+ for column in other.headers:
+ _dset.append(col=other[column])
+
+ _dset.headers = new_headers
+
+ return _dset
+
def wipe(self):
"""Removes all content and headers from the :class:`Dataset` object."""
self._data = list()
@@ -537,7 +631,7 @@ class Databook(object):
"""Removes all :class:`Dataset` objects from the :class:`Databook`."""
self._datasets = []
-
+
@classmethod
def _register_formats(cls):
"""Adds format properties."""
@@ -547,7 +641,7 @@ class Databook(object):
setattr(cls, fmt.title, property(fmt.export_book, fmt.import_book))
except AttributeError:
setattr(cls, fmt.title, property(fmt.export_book))
-
+
except AttributeError:
pass
@@ -558,7 +652,7 @@ class Databook(object):
self._datasets.append(dataset)
else:
raise InvalidDatasetType
-
+
def _package(self):
"""Packages :class:`Databook` for delivery."""
@@ -582,12 +676,12 @@ def detect(stream):
for fmt in formats.available:
try:
if fmt.detect(stream):
- return (fmt, stream)
+ return (fmt, stream)
except AttributeError:
- pass
+ pass
return (None, stream)
-
-
+
+
def import_set(stream):
"""Return dataset of given stream."""
(format, stream) = detect(stream)
@@ -596,7 +690,7 @@ def import_set(stream):
data = Dataset()
format.import_set(data, stream)
return data
-
+
except AttributeError, e:
return None
diff --git a/tablib/formats/_xls.py b/tablib/formats/_xls.py
index 97a9580..08bc0f6 100644
--- a/tablib/formats/_xls.py
+++ b/tablib/formats/_xls.py
@@ -26,7 +26,7 @@ def export_set(dataset):
ws = wb.add_sheet(dataset.title if dataset.title else 'Tabbed Dataset')
dset_sheet(dataset, ws)
-
+
stream = cStringIO.StringIO()
wb.save(stream)
return stream.getvalue()
@@ -63,6 +63,11 @@ def dset_sheet(dataset, ws):
if (i == 0) and dataset.headers:
ws.write(i, j, col, bold)
+ # frozen header row
+ ws.panes_frozen = True
+ ws.horz_split_pos = 1
+
+
# bold separators
elif len(row) < dataset.width:
ws.write(i, j, col, bold)
@@ -77,4 +82,4 @@ def dset_sheet(dataset, ws):
except TypeError:
ws.write(i, j, col)
- \ No newline at end of file
+
diff --git a/tablib/packages/ordereddict.py b/tablib/packages/ordereddict.py
new file mode 100644
index 0000000..5b0303f
--- /dev/null
+++ b/tablib/packages/ordereddict.py
@@ -0,0 +1,127 @@
+# Copyright (c) 2009 Raymond Hettinger
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation files
+# (the "Software"), to deal in the Software without restriction,
+# including without limitation the rights to use, copy, modify, merge,
+# publish, distribute, sublicense, and/or sell copies of the Software,
+# and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+# OTHER DEALINGS IN THE SOFTWARE.
+
+from UserDict import DictMixin
+
+class OrderedDict(dict, DictMixin):
+
+ def __init__(self, *args, **kwds):
+ if len(args) > 1:
+ raise TypeError('expected at most 1 arguments, got %d' % len(args))
+ try:
+ self.__end
+ except AttributeError:
+ self.clear()
+ self.update(*args, **kwds)
+
+ def clear(self):
+ self.__end = end = []
+ end += [None, end, end] # sentinel node for doubly linked list
+ self.__map = {} # key --> [key, prev, next]
+ dict.clear(self)
+
+ def __setitem__(self, key, value):
+ if key not in self:
+ end = self.__end
+ curr = end[1]
+ curr[2] = end[1] = self.__map[key] = [key, curr, end]
+ dict.__setitem__(self, key, value)
+
+ def __delitem__(self, key):
+ dict.__delitem__(self, key)
+ key, prev, next = self.__map.pop(key)
+ prev[2] = next
+ next[1] = prev
+
+ def __iter__(self):
+ end = self.__end
+ curr = end[2]
+ while curr is not end:
+ yield curr[0]
+ curr = curr[2]
+
+ def __reversed__(self):
+ end = self.__end
+ curr = end[1]
+ while curr is not end:
+ yield curr[0]
+ curr = curr[1]
+
+ def popitem(self, last=True):
+ if not self:
+ raise KeyError('dictionary is empty')
+ if last:
+ key = reversed(self).next()
+ else:
+ key = iter(self).next()
+ value = self.pop(key)
+ return key, value
+
+ def __reduce__(self):
+ items = [[k, self[k]] for k in self]
+ tmp = self.__map, self.__end
+ del self.__map, self.__end
+ inst_dict = vars(self).copy()
+ self.__map, self.__end = tmp
+ if inst_dict:
+ return (self.__class__, (items,), inst_dict)
+ return self.__class__, (items,)
+
+ def keys(self):
+ return list(self)
+
+ setdefault = DictMixin.setdefault
+ update = DictMixin.update
+ pop = DictMixin.pop
+ values = DictMixin.values
+ items = DictMixin.items
+ iterkeys = DictMixin.iterkeys
+ itervalues = DictMixin.itervalues
+ iteritems = DictMixin.iteritems
+
+ def __repr__(self):
+ if not self:
+ return '%s()' % (self.__class__.__name__,)
+ return '%s(%r)' % (self.__class__.__name__, self.items())
+
+ def copy(self):
+ return self.__class__(self)
+
+ @classmethod
+ def fromkeys(cls, iterable, value=None):
+ d = cls()
+ for key in iterable:
+ d[key] = value
+ return d
+
+ def __eq__(self, other):
+ if isinstance(other, OrderedDict):
+ if len(self) != len(other):
+ return False
+ for p, q in zip(self.items(), other.items()):
+ if p != q:
+ return False
+ return True
+ return dict.__eq__(self, other)
+
+ def __ne__(self, other):
+ return not self == other
diff --git a/test_tablib.py b/test_tablib.py
index 342fd45..8e2454f 100755
--- a/test_tablib.py
+++ b/test_tablib.py
@@ -8,6 +8,7 @@ import unittest
import tablib
+
class TablibTestCase(unittest.TestCase):
"""Tablib test cases."""
@@ -15,6 +16,7 @@ class TablibTestCase(unittest.TestCase):
"""Create simple data set with headers."""
global data, book
+
data = tablib.Dataset()
book = tablib.Databook()
@@ -192,7 +194,7 @@ class TablibTestCase(unittest.TestCase):
data.tsv
data.xls
-
+
def test_book_export_no_exceptions(self):
"""Test that varoius exports don't error out."""
@@ -243,7 +245,7 @@ class TablibTestCase(unittest.TestCase):
self.assertEqual(_yaml, data.yaml)
-
+
def test_yaml_import_book(self):
"""Generate and import YAML book serialization."""
data.append(self.john)
@@ -256,7 +258,7 @@ class TablibTestCase(unittest.TestCase):
book.yaml = _yaml
self.assertEqual(_yaml, book.yaml)
-
+
def test_csv_import_set(self):
"""Generate and import CSV set serialization."""
@@ -284,7 +286,7 @@ class TablibTestCase(unittest.TestCase):
def test_csv_format_detect(self):
"""Test CSV format detection."""
-
+
_csv = (
'1,2,3\n'
'4,5,6\n'
@@ -293,13 +295,13 @@ class TablibTestCase(unittest.TestCase):
_bunk = (
'¡¡¡¡¡¡¡¡£™∞¢£§∞§¶•¶ª∞¶•ªº••ª–º§•†•§º¶•†¥ª–º•§ƒø¥¨©πƒø†ˆ¥ç©¨√øˆ¥≈†ƒ¥ç©ø¨çˆ¥ƒçø¶'
)
-
+
self.assertTrue(tablib.formats.csv.detect(_csv))
self.assertFalse(tablib.formats.csv.detect(_bunk))
def test_tsv_format_detect(self):
"""Test TSV format detection."""
-
+
_tsv = (
'1\t2\t3\n'
'4\t5\t6\n'
@@ -308,7 +310,7 @@ class TablibTestCase(unittest.TestCase):
_bunk = (
'¡¡¡¡¡¡¡¡£™∞¢£§∞§¶•¶ª∞¶•ªº••ª–º§•†•§º¶•†¥ª–º•§ƒø¥¨©πƒø†ˆ¥ç©¨√øˆ¥≈†ƒ¥ç©ø¨çˆ¥ƒçø¶'
)
-
+
self.assertTrue(tablib.formats.tsv.detect(_tsv))
self.assertFalse(tablib.formats.tsv.detect(_bunk))
@@ -349,23 +351,74 @@ class TablibTestCase(unittest.TestCase):
self.assertEqual(tablib.detect(_json)[0], tablib.formats.json)
self.assertEqual(tablib.detect(_bunk)[0], None)
+ def test_transpose(self):
+ """Transpose a dataset."""
+
+ transposed_founders = self.founders.transpose()
+ first_row = transposed_founders[0]
+ second_row = transposed_founders[1]
+
+ self.assertEqual(transposed_founders.headers,
+ ["first_name","John", "George", "Thomas"])
+ self.assertEqual(first_row,
+ ("last_name","Adams", "Washington", "Jefferson"))
+ self.assertEqual(second_row,
+ ("gpa",90, 67, 50))
+
+ def test_row_stacking(self):
+
+ """Row stacking."""
+
+ to_join = tablib.Dataset(headers=self.founders.headers)
+
+ for row in self.founders:
+ to_join.append(row=row)
+
+ row_stacked = self.founders.stack_rows(to_join)
+
+ for column in row_stacked.headers:
+
+ original_data = self.founders[column]
+ expected_data = original_data + original_data
+ self.assertEqual(row_stacked[column], expected_data)
+
+ def test_column_stacking(self):
+
+ """Column stacking"""
+
+ to_join = tablib.Dataset(headers=self.founders.headers)
+
+ for row in self.founders:
+ to_join.append(row=row)
+
+ column_stacked = self.founders.stack_columns(to_join)
+
+ for index, row in enumerate(column_stacked):
+
+ original_data = self.founders[index]
+ expected_data = original_data + original_data
+ self.assertEqual(row, expected_data)
+
+ self.assertEqual(column_stacked[0],
+ ("John", "Adams", 90, "John", "Adams", 90))
+
def test_wipe(self):
"""Purge a dataset."""
-
+
new_row = (1, 2, 3)
data.append(new_row)
# Verify width/data
self.assertTrue(data.width == len(new_row))
self.assertTrue(data[0] == new_row)
-
+
data.wipe()
new_row = (1, 2, 3, 4)
data.append(new_row)
self.assertTrue(data.width == len(new_row))
self.assertTrue(data[0] == new_row)
-
-
+
+
if __name__ == '__main__':
unittest.main()