mirror of
https://github.com/kennethreitz/tablib.git
synced 2026-06-05 15:00:19 +00:00
Compare commits
29 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 7727171379 | |||
| 91bd4eb9c7 | |||
| 9b74b139fd | |||
| 823a543f41 | |||
| 1aa275bf99 | |||
| 17bb0d3b2c | |||
| 1a9aee9289 | |||
| 196edb82cc | |||
| a2990d5852 | |||
| d992ece86a | |||
| 46f302255d | |||
| 9e3ab4c13f | |||
| eaed0e48c2 | |||
| 501187b357 | |||
| ea4aef88b6 | |||
| 24d800fac3 | |||
| d8136ab613 | |||
| 36bbe2726b | |||
| 1427be2901 | |||
| 10ce000d31 | |||
| a91254117c | |||
| b67762604f | |||
| 83a8346e8f | |||
| 657ab98d04 | |||
| 9ddb4de942 | |||
| 5fad80a540 | |||
| cabab73045 | |||
| 2bb0525990 | |||
| f364bb576e |
@@ -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)
|
||||
++++++++++++++++++
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
|
||||
|
||||
+2
-3
@@ -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
|
||||
----------
|
||||
|
||||
|
||||
@@ -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)
|
||||
+3
-3
@@ -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>`.
|
||||
Now, go check out the :ref:`API Documentation <api>` or begin :ref:`Tablib Development <development>`.
|
||||
|
||||
@@ -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(),
|
||||
|
||||
+165
-71
@@ -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
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
+64
-11
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user