diff options
| -rw-r--r-- | tablib/core.py | 308 | ||||
| -rwxr-xr-x | test_tablib.py | 30 |
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") |
