summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--tablib/core.py308
-rwxr-xr-xtest_tablib.py30
2 files changed, 213 insertions, 125 deletions
diff --git a/tablib/core.py b/tablib/core.py
index 16b3dff..9c561fd 100644
--- a/tablib/core.py
+++ b/tablib/core.py
@@ -62,8 +62,14 @@ class Row(object):
def __setstate__(self, state):
for (k, v) in list(state.items()): setattr(self, k, v)
+ def rpush(self, value):
+ self.insert(0, value)
+
+ def lpush(self, value):
+ self.insert(len(value), value)
+
def append(self, value):
- self._row.append(value)
+ self.rpush(value)
def insert(self, index, value):
self._row.insert(index, value)
@@ -200,6 +206,10 @@ class Dataset(object):
return '<dataset object>'
+ # ---------
+ # Internals
+ # ---------
+
@classmethod
def _register_formats(cls):
"""Adds format properties."""
@@ -236,6 +246,7 @@ class Dataset(object):
def _package(self, dicts=True, ordered=True):
"""Packages Dataset into lists of dictionaries for transmission."""
+ # TODO: Dicts default to false?
_data = list(self._data)
@@ -269,46 +280,6 @@ class Dataset(object):
return data
- 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 hasattr(col[0], '__call__'):
-
- col = list(map(col[0], self._data))
- col = tuple(header + col)
-
- return col
-
-
- @property
- def height(self):
- """The number of rows currently in the :class:`Dataset`.
- Cannot be directly modified.
- """
- return len(self._data)
-
-
- @property
- def width(self):
- """The number of columns currently in the :class:`Dataset`.
- Cannot be directly modified.
- """
-
- try:
- return len(self._data[0])
- except IndexError:
- try:
- return len(self.headers)
- except TypeError:
- return 0
-
def _get_headers(self):
"""An *optional* list of strings to be used for header rows and attribute names.
@@ -332,6 +303,7 @@ class Dataset(object):
headers = property(_get_headers, _set_headers)
+
def _get_dict(self):
"""A native Python representation of the :class:`Dataset` object. If headers have
been set, a list of Python dictionaries will be returned. If no headers have been set,
@@ -379,6 +351,52 @@ class Dataset(object):
dict = property(_get_dict, _set_dict)
+ 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 hasattr(col[0], '__call__'):
+
+ col = list(map(col[0], self._data))
+ col = tuple(header + col)
+
+ return col
+
+
+ @property
+ def height(self):
+ """The number of rows currently in the :class:`Dataset`.
+ Cannot be directly modified.
+ """
+ return len(self._data)
+
+
+ @property
+ def width(self):
+ """The number of columns currently in the :class:`Dataset`.
+ Cannot be directly modified.
+ """
+
+ try:
+ return len(self._data[0])
+ except IndexError:
+ try:
+ return len(self.headers)
+ except TypeError:
+ return 0
+
+
+ # -------
+ # Formats
+ # -------
+
+
@property
def xls():
"""A Legacy Excel Spreadsheet representation of the :class:`Dataset` object, with :ref:`separators`. Cannot be set.
@@ -493,15 +511,128 @@ class Dataset(object):
pass
- def append(self, row=None, col=None, header=None, tags=list()):
- """Adds a row or column to the :class:`Dataset`.
- Usage is :class:`Dataset.insert` for documentation.
+ # ----
+ # Rows
+ # ----
+
+ def insert(self, index, row, tags=list()):
+ """Inserts a row 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
+ item in the column. ::
+
+ data.append(col=random.randint)
+
+ See :ref:`dyncols` for an in-depth example.
+
+ .. versionchanged:: 0.9.0
+ If inserting a column, and :class:`Dataset.headers` is set, the
+ header attribute must be set, and will be considered the header for
+ that row.
+
+ .. versionadded:: 0.9.0
+ 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.
+ """
+
+ self._validate(row)
+ self._data.insert(index, Row(row, tags=tags))
+
+
+ def rpush(self, row, tags=list()):
+ """Adds a row to the end of the :class:`Dataset`.
+ See :class:`Dataset.insert` for additional documentation.
+ """
+
+ self.insert(self.height, row=row, tags=tags)
+
+
+ def lpush(self, row, tags=list()):
+ """Adds a row to the top of the :class:`Dataset`.
+ See :class:`Dataset.insert` for additional documentation.
+ """
+
+ self.insert(0, row=row, tags=tags)
+
+
+ def append(self, row, tags=list()):
+ """Adds a row to the :class:`Dataset`.
+ See :class:`Dataset.insert` for additional documentation.
+ """
+
+ self.rpush(row, tags)
+
+
+ # -------
+ # Columns
+ # -------
+
+ def insert_col(self, index, col=None, header=None):
+ """Inserts a column to the :class:`Dataset` at the given index.
+
+ Columns inserted must be the correct height.
+
+ 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
+ item in the column. ::
+
+ data.append_col(col=random.randint)
+
+ If inserting a column, and :class:`Dataset.headers` is set, the
+ header attribute must be set, and will be considered the header for
+ that row.
+
+ See :ref:`dyncols` for an in-depth example.
+ """
+
+ col = list(col)
+
+ # Callable Columns...
+ if len(col) == 1 and hasattr(col[0], '__call__'):
+ col = list(map(col[0], self._data))
+
+ col = self._clean_col(col)
+ self._validate(col=col)
+
+ if self.headers:
+ # pop the first item off, add to headers
+ if not header:
+ raise HeadersNeeded()
+ self.headers.insert(index, header)
+
+ if self.height and self.width:
+
+ for i, row in enumerate(self._data):
+
+ row.insert(index, col[i])
+ self._data[i] = row
+ else:
+ self._data = [Row([row]) for row in col]
+
+
+
+ def rpush_col(self, col, header=None):
+ """Adds a column to the end of the :class:`Dataset`.
+ See :class:`Dataset.insert` for additional documentation.
"""
- if row is not None:
- self.insert(self.height, row=row, tags=tags)
- elif col is not None:
- self.insert(self.width, col=col, header=header)
+ self.insert_col(self.width, col, header=header)
+
+
+ def lpush_col(self, col, header=None):
+ """Adds a column to the top of the :class:`Dataset`.
+ See :class:`Dataset.insert` for additional documentation.
+ """
+
+ self.insert_col(0, col, header=header)
def insert_separator(self, index, text='-'):
@@ -523,6 +654,18 @@ class Dataset(object):
self.insert_separator(index, text)
+ def append_col(self, col, header=None):
+ """Adds a column to the :class:`Dataset`.
+ See :class:`Dataset.insert_col` for additional documentation.
+ """
+
+ self.rpush_col(col, header)
+
+
+ # ----
+ # Misc
+ # ----
+
def add_formatter(self, col, handler):
"""Adds a :ref:`formatter` to the :class:`Dataset`.
@@ -546,63 +689,6 @@ class Dataset(object):
return True
- 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).
-
- 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
- item in the column. ::
-
- data.append(col=random.randint)
-
- See :ref:`dyncols` for an in-depth example.
-
- .. versionchanged:: 0.9.0
- If inserting a column, and :class:`Dataset.headers` is set, the
- header attribute must be set, and will be considered the header for
- that row.
-
- .. versionadded:: 0.9.0
- 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)
- self._data.insert(index, Row(row, tags=tags))
- elif col:
- col = list(col)
-
- # Callable Columns...
- if len(col) == 1 and hasattr(col[0], '__call__'):
- col = list(map(col[0], self._data))
-
- col = self._clean_col(col)
- self._validate(col=col)
-
- if self.headers:
- # pop the first item off, add to headers
- if not header:
- raise HeadersNeeded()
- self.headers.insert(index, header)
-
- if self.height and self.width:
-
- for i, row in enumerate(self._data):
-
- row.insert(index, col[i])
- self._data[i] = row
- else:
- self._data = [Row([row]) for row in col]
-
-
def filter(self, tag):
"""Returns a new instance of the :class:`Dataset`, excluding any rows
that do not contain the given :ref:`tags <tags>`.
@@ -617,8 +703,10 @@ class Dataset(object):
"""Sort a :class:`Dataset` by a specific column, given string (for
header) or integer (for column index). The order can be reversed by
setting ``reverse`` to ``True``.
+
Returns a new :class:`Dataset` instance where columns have been
- sorted."""
+ sorted.
+ """
if isinstance(col, str):
@@ -679,7 +767,7 @@ class Dataset(object):
return _dset
- def stack_rows(self, other):
+ def stack(self, other):
"""Stack two :class:`Dataset` instances together by
joining at the row level, and return new combined
``Dataset`` instance."""
@@ -702,7 +790,7 @@ class Dataset(object):
return _dset
- def stack_columns(self, other):
+ def stack_cols(self, other):
"""Stack two :class:`Dataset` instances together by
joining at the column level, and return a new
combined ``Dataset`` instance. If either ``Dataset``
@@ -726,10 +814,10 @@ class Dataset(object):
_dset = Dataset()
for column in self.headers:
- _dset.append(col=self[column])
+ _dset.append_col(col=self[column])
for column in other.headers:
- _dset.append(col=other[column])
+ _dset.append_col(col=other[column])
_dset.headers = new_headers
diff --git a/test_tablib.py b/test_tablib.py
index c5210a9..860c5a8 100755
--- a/test_tablib.py
+++ b/test_tablib.py
@@ -73,7 +73,7 @@ class TablibTestCase(unittest.TestCase):
new_col = ['reitz', 'monke']
- data.append(col=new_col)
+ data.append_col(new_col)
self.assertEquals(data[0], ('kenneth', 'reitz'))
self.assertEquals(data.width, 2)
@@ -81,7 +81,7 @@ class TablibTestCase(unittest.TestCase):
# With Headers
data.headers = ('fname', 'lname')
new_col = [21, 22]
- data.append(col=new_col, header='age')
+ data.append_col(new_col, header='age')
self.assertEquals(data['age'], new_col)
@@ -91,7 +91,7 @@ class TablibTestCase(unittest.TestCase):
new_col = ('reitz', 'monke')
- data.append(col=new_col)
+ data.append_col(new_col)
self.assertEquals(data[0], tuple([new_col[0]]))
self.assertEquals(data.width, 1)
@@ -100,21 +100,23 @@ class TablibTestCase(unittest.TestCase):
def test_add_callable_column(self):
"""Verify adding column with values specified as callable."""
+
new_col = [lambda x: x[0]]
- self.founders.append(col=new_col, header='first_again')
-#
-# self.assertTrue(map(lambda x: x[0] == x[-1], self.founders))
+
+ self.founders.append_col(new_col, header='first_again')
def test_header_slicing(self):
"""Verify slicing by headers."""
self.assertEqual(self.founders['first_name'],
- [self.john[0], self.george[0], self.tom[0]])
+ [self.john[0], self.george[0], self.tom[0]])
+
self.assertEqual(self.founders['last_name'],
- [self.john[1], self.george[1], self.tom[1]])
+ [self.john[1], self.george[1], self.tom[1]])
+
self.assertEqual(self.founders['gpa'],
- [self.john[2], self.george[2], self.tom[2]])
+ [self.john[2], self.george[2], self.tom[2]])
def test_data_slicing(self):
@@ -174,6 +176,7 @@ class TablibTestCase(unittest.TestCase):
self.assertEqual(csv, self.founders.csv)
+
def test_tsv_export(self):
"""Verify exporting dataset object as CSV."""
@@ -191,8 +194,8 @@ class TablibTestCase(unittest.TestCase):
self.assertEqual(tsv, self.founders.tsv)
- def test_html_export(self):
+ def test_html_export(self):
"""HTML export"""
html = markup.page()
@@ -421,7 +424,6 @@ class TablibTestCase(unittest.TestCase):
def test_row_stacking(self):
-
"""Row stacking."""
to_join = tablib.Dataset(headers=self.founders.headers)
@@ -429,7 +431,7 @@ class TablibTestCase(unittest.TestCase):
for row in self.founders:
to_join.append(row=row)
- row_stacked = self.founders.stack_rows(to_join)
+ row_stacked = self.founders.stack(to_join)
for column in row_stacked.headers:
@@ -439,7 +441,6 @@ class TablibTestCase(unittest.TestCase):
def test_column_stacking(self):
-
"""Column stacking"""
to_join = tablib.Dataset(headers=self.founders.headers)
@@ -447,7 +448,7 @@ class TablibTestCase(unittest.TestCase):
for row in self.founders:
to_join.append(row=row)
- column_stacked = self.founders.stack_columns(to_join)
+ column_stacked = self.founders.stack_cols(to_join)
for index, row in enumerate(column_stacked):
@@ -460,7 +461,6 @@ class TablibTestCase(unittest.TestCase):
def test_sorting(self):
-
"""Sort columns."""
sorted_data = self.founders.sort(col="first_name")