From 69abfc3ada5d754cb152119c0b4777043657cb6e Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Tue, 13 Jun 2017 12:29:55 -0400 Subject: use safe load --- tablib/formats/_yaml.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'tablib') diff --git a/tablib/formats/_yaml.py b/tablib/formats/_yaml.py index 5aecb42..3d17baf 100644 --- a/tablib/formats/_yaml.py +++ b/tablib/formats/_yaml.py @@ -33,7 +33,7 @@ def import_book(dbook, in_stream): dbook.wipe() - for sheet in yaml.load(in_stream): + for sheet in yaml.safe_load(in_stream): data = tablib.Dataset() data.title = sheet['title'] data.dict = sheet['data'] -- cgit v1.2.1 From d89d243a30d453a349d2f630728e72ac452815c4 Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Tue, 13 Jun 2017 12:30:27 -0400 Subject: v0.11.5 --- tablib/core.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'tablib') diff --git a/tablib/core.py b/tablib/core.py index c44c6ac..be648c2 100644 --- a/tablib/core.py +++ b/tablib/core.py @@ -18,11 +18,11 @@ from tablib.compat import OrderedDict, unicode __title__ = 'tablib' -__version__ = '0.11.4' +__version__ = '0.11.5' __build__ = 0x001104 __author__ = 'Kenneth Reitz' __license__ = 'MIT' -__copyright__ = 'Copyright 2016 Kenneth Reitz' +__copyright__ = 'Copyright 2017 Kenneth Reitz' __docformat__ = 'restructuredtext' -- cgit v1.2.1 From 00e2ffa2efd1a7dd47d2a4a2a4303e1a8beb71e3 Mon Sep 17 00:00:00 2001 From: Jason Myers Date: Sat, 26 Aug 2017 20:43:35 -0500 Subject: Adding initial DataFrames Support Signed-off-by: Jason Myers --- tablib/core.py | 12 ++++++++++++ tablib/formats/__init__.py | 3 ++- tablib/formats/_df.py | 40 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 54 insertions(+), 1 deletion(-) create mode 100644 tablib/formats/_df.py (limited to 'tablib') diff --git a/tablib/core.py b/tablib/core.py index be648c2..13a4514 100644 --- a/tablib/core.py +++ b/tablib/core.py @@ -570,6 +570,18 @@ class Dataset(object): """ pass + @property + def df(): + """A DataFrame representation of the :class:`Dataset` object. + + A dataset object can also be imported by setting the :class:`Dataset.df` attribute: :: + + data = tablib.Dataset() + data.df = DataFrame(np.random.randn(6,4)) + + Import assumes (for now) that headers exist. + """ + pass @property def json(): diff --git a/tablib/formats/__init__.py b/tablib/formats/__init__.py index 5cca19f..94b5bc9 100644 --- a/tablib/formats/__init__.py +++ b/tablib/formats/__init__.py @@ -13,5 +13,6 @@ from . import _xlsx as xlsx from . import _ods as ods from . import _dbf as dbf from . import _latex as latex +from . import _df as df -available = (json, xls, yaml, csv, dbf, tsv, html, latex, xlsx, ods) +available = (json, xls, yaml, csv, dbf, tsv, html, latex, xlsx, ods, df) diff --git a/tablib/formats/_df.py b/tablib/formats/_df.py new file mode 100644 index 0000000..5996ce9 --- /dev/null +++ b/tablib/formats/_df.py @@ -0,0 +1,40 @@ +""" Tablib - DataFrame Support. +""" + + +import sys + + +if sys.version_info[0] > 2: + from io import BytesIO +else: + from cStringIO import StringIO as BytesIO + +from pandas import DataFrame + +import tablib + +from tablib.compat import unicode + +title = 'df' +extensions = ('df', ) + +def detect(stream): + """Returns True if given stream is a DataFrame.""" + try: + DataFrame(stream) + return True + except ValueError: + return False + + +def export_set(dset, index=None): + """Returns DataFrame representation of DataBook.""" + dataframe = DataFrame(dset.dict, columns=dset.headers) + return dataframe + + +def import_set(dset, in_stream): + """Returns dataset from DataFrame.""" + dset.wipe() + dset.dict = in_stream.to_dict(orient='records') -- cgit v1.2.1 From 5dd74c0104789b2019c97f863e597369ccfed108 Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Sun, 27 Aug 2017 03:29:44 -0400 Subject: drop 2.6 --- tablib/core.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'tablib') diff --git a/tablib/core.py b/tablib/core.py index 13a4514..dc45a6f 100644 --- a/tablib/core.py +++ b/tablib/core.py @@ -18,8 +18,8 @@ from tablib.compat import OrderedDict, unicode __title__ = 'tablib' -__version__ = '0.11.5' -__build__ = 0x001104 +__version__ = '0.12.0' +__build__ = 0x001200 __author__ = 'Kenneth Reitz' __license__ = 'MIT' __copyright__ = 'Copyright 2017 Kenneth Reitz' -- cgit v1.2.1 From 7f1db4023f2310529822d721379b1019aaf320fc Mon Sep 17 00:00:00 2001 From: Ryan Castner Date: Fri, 1 Sep 2017 13:21:21 -0400 Subject: Raise NotImplementedError if pandas is not installed --- tablib/formats/_df.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) (limited to 'tablib') diff --git a/tablib/formats/_df.py b/tablib/formats/_df.py index 5996ce9..44b967f 100644 --- a/tablib/formats/_df.py +++ b/tablib/formats/_df.py @@ -10,7 +10,10 @@ if sys.version_info[0] > 2: else: from cStringIO import StringIO as BytesIO -from pandas import DataFrame +try: + from pandas import DataFrame +except ImportError: + DataFrame = None import tablib @@ -21,6 +24,8 @@ extensions = ('df', ) def detect(stream): """Returns True if given stream is a DataFrame.""" + if DataFrame is None: + return False try: DataFrame(stream) return True @@ -30,6 +35,10 @@ def detect(stream): def export_set(dset, index=None): """Returns DataFrame representation of DataBook.""" + if DataFrame is None: + raise NotImplementedError( + 'DataFrame Format requires `pandas` to be installed.' + ' Try `pip install tablib[pandas]`.') dataframe = DataFrame(dset.dict, columns=dset.headers) return dataframe -- cgit v1.2.1 From edbb16ec97bbecc679b0ebc9de6f09f7b942bf8b Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Fri, 1 Sep 2017 15:37:00 -0400 Subject: next version --- tablib/core.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'tablib') diff --git a/tablib/core.py b/tablib/core.py index dc45a6f..8c49f2e 100644 --- a/tablib/core.py +++ b/tablib/core.py @@ -18,8 +18,8 @@ from tablib.compat import OrderedDict, unicode __title__ = 'tablib' -__version__ = '0.12.0' -__build__ = 0x001200 +__version__ = '0.12.1' +__build__ = 0x001201 __author__ = 'Kenneth Reitz' __license__ = 'MIT' __copyright__ = 'Copyright 2017 Kenneth Reitz' -- cgit v1.2.1 From 61063e2b09a5cd26926311a34826fd33be0b1757 Mon Sep 17 00:00:00 2001 From: DougHudgeon Date: Mon, 25 Jun 2018 14:17:34 +1000 Subject: Updated xlsx format to use openpyxl's .active property --- tablib/formats/_xlsx.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'tablib') diff --git a/tablib/formats/_xlsx.py b/tablib/formats/_xlsx.py index 20f55df..67aa5f4 100644 --- a/tablib/formats/_xlsx.py +++ b/tablib/formats/_xlsx.py @@ -71,7 +71,7 @@ def import_set(dset, in_stream, headers=True): dset.wipe() xls_book = openpyxl.reader.excel.load_workbook(BytesIO(in_stream)) - sheet = xls_book.get_active_sheet() + sheet = xls_book.active dset.title = sheet.title -- cgit v1.2.1 From f812c29275c7f47b08f8f1b962625545ec4e22ad Mon Sep 17 00:00:00 2001 From: DougHudgeon Date: Tue, 26 Jun 2018 10:33:21 +1000 Subject: Add instructions for handling csv line endings in Windows in Python 3 --- tablib/core.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) (limited to 'tablib') diff --git a/tablib/core.py b/tablib/core.py index 8c49f2e..e0d61f0 100644 --- a/tablib/core.py +++ b/tablib/core.py @@ -526,9 +526,9 @@ class Dataset(object): Import assumes (for now) that headers exist. - .. admonition:: Binary Warning + .. admonition:: Binary Warning for Python 2 - :class:`Dataset.csv` uses \\r\\n line endings by default, so make + :class:`Dataset.csv` uses \\r\\n line endings by default so, in Python 2, make sure to write in binary mode:: with open('output.csv', 'wb') as f: @@ -536,6 +536,18 @@ class Dataset(object): If you do not do this, and you export the file on Windows, your CSV file will open in Excel with a blank line between each row. + + .. admonition:: Line endings for Python 3 + + :class:`Dataset.csv` uses \\r\\n line endings by default so, in Python 3, make + sure to include newline='' otherwise you will get a blank line between each row + when you open the file in Excel:: + + with open('output.csv', 'w', newline='') as f: + f.write(data.csv) + + If you do not do this, and you export the file on Windows, your + CSV file will open in Excel with a blank line between each row. """ pass -- cgit v1.2.1 From ac3cf67620c89f71b73bed891355e8dc0491a541 Mon Sep 17 00:00:00 2001 From: Gregory Bataille Date: Wed, 12 Sep 2018 13:34:55 +0200 Subject: fix(): remove openpyxl warning by properly accessing cells (#296) --- tablib/formats/_xlsx.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'tablib') diff --git a/tablib/formats/_xlsx.py b/tablib/formats/_xlsx.py index 20f55df..eb921f9 100644 --- a/tablib/formats/_xlsx.py +++ b/tablib/formats/_xlsx.py @@ -119,7 +119,7 @@ def dset_sheet(dataset, ws, freeze_panes=True): row_number = i + 1 for j, col in enumerate(row): col_idx = get_column_letter(j + 1) - cell = ws.cell('%s%s' % (col_idx, row_number)) + cell = ws['%s%s' % (col_idx, row_number)] # bold headers if (row_number == 1) and dataset.headers: -- cgit v1.2.1 From 38486231cc4dc46d1ff055a38fe7c236cbe5c8aa Mon Sep 17 00:00:00 2001 From: Norman Hooper Date: Wed, 12 Sep 2018 20:27:10 +0200 Subject: reStructuredText (#336) * median for Python 2 * More compat * Support reStructuredText * Tests --- tablib/compat.py | 5 +- tablib/formats/__init__.py | 3 +- tablib/formats/_rst.py | 273 ++++++++++++++++++++++++++++++++++++++++++ tablib/packages/statistics.py | 24 ++++ 4 files changed, 303 insertions(+), 2 deletions(-) create mode 100644 tablib/formats/_rst.py create mode 100644 tablib/packages/statistics.py (limited to 'tablib') diff --git a/tablib/compat.py b/tablib/compat.py index 43e0bbc..d60ce72 100644 --- a/tablib/compat.py +++ b/tablib/compat.py @@ -22,6 +22,8 @@ except ImportError: if is_py3: from io import BytesIO + from itertools import zip_longest as izip_longest + from statistics import median from tablib.packages import markup3 as markup import tablib.packages.dbfpy3 as dbfpy @@ -39,7 +41,8 @@ else: from cStringIO import StringIO as BytesIO from cStringIO import StringIO from tablib.packages import markup - from itertools import ifilter + from tablib.packages.statistics import median + from itertools import ifilter, izip_longest import unicodecsv as csv import tablib.packages.dbfpy as dbfpy diff --git a/tablib/formats/__init__.py b/tablib/formats/__init__.py index 94b5bc9..3eb747e 100644 --- a/tablib/formats/__init__.py +++ b/tablib/formats/__init__.py @@ -14,5 +14,6 @@ from . import _ods as ods from . import _dbf as dbf from . import _latex as latex from . import _df as df +from . import _rst as rst -available = (json, xls, yaml, csv, dbf, tsv, html, latex, xlsx, ods, df) +available = (json, xls, yaml, csv, dbf, tsv, html, latex, xlsx, ods, df, rst) diff --git a/tablib/formats/_rst.py b/tablib/formats/_rst.py new file mode 100644 index 0000000..4b53ad7 --- /dev/null +++ b/tablib/formats/_rst.py @@ -0,0 +1,273 @@ +# -*- coding: utf-8 -*- + +""" Tablib - reStructuredText Support +""" +from __future__ import division +from __future__ import print_function +from __future__ import unicode_literals + +from textwrap import TextWrapper + +from tablib.compat import ( + median, + unicode, + izip_longest, +) + + +title = 'rst' +extensions = ('rst',) + + +MAX_TABLE_WIDTH = 80 # Roughly. It may be wider to avoid breaking words. + + +JUSTIFY_LEFT = 'left' +JUSTIFY_CENTER = 'center' +JUSTIFY_RIGHT = 'right' +JUSTIFY_VALUES = (JUSTIFY_LEFT, JUSTIFY_CENTER, JUSTIFY_RIGHT) + + +def to_unicode(value): + if isinstance(value, bytes): + return value.decode('utf-8') + return unicode(value) + + +def _max_word_len(text): + """ + Return the length of the longest word in `text`. + + + >>> _max_word_len('Python Module for Tabular Datasets') + 8 + + """ + return max((len(word) for word in text.split())) + + +def _get_column_string_lengths(dataset): + """ + Returns a list of string lengths of each column, and a list of + maximum word lengths. + """ + if dataset.headers: + column_lengths = [[len(h)] for h in dataset.headers] + word_lens = [_max_word_len(h) for h in dataset.headers] + else: + column_lengths = [[] for _ in range(dataset.width)] + word_lens = [0 for _ in range(dataset.width)] + for row in dataset.dict: + values = iter(row.values() if hasattr(row, 'values') else row) + for i, val in enumerate(values): + text = to_unicode(val) + column_lengths[i].append(len(text)) + word_lens[i] = max(word_lens[i], _max_word_len(text)) + return column_lengths, word_lens + + +def _row_to_lines(values, widths, wrapper, sep='|', justify=JUSTIFY_LEFT): + """ + Returns a table row of wrapped values as a list of lines + """ + if justify not in JUSTIFY_VALUES: + raise ValueError('Value of "justify" must be one of "{}"'.format( + '", "'.join(JUSTIFY_VALUES) + )) + if justify == JUSTIFY_LEFT: + just = lambda text, width: text.ljust(width) + elif justify == JUSTIFY_CENTER: + just = lambda text, width: text.center(width) + else: + just = lambda text, width: text.rjust(width) + lpad = sep + ' ' if sep else '' + rpad = ' ' + sep if sep else '' + pad = ' ' + sep + ' ' + cells = [] + for value, width in zip(values, widths): + wrapper.width = width + text = to_unicode(value) + cell = wrapper.wrap(text) + cells.append(cell) + lines = izip_longest(*cells, fillvalue='') + lines = ( + (just(cell_line, widths[i]) for i, cell_line in enumerate(line)) + for line in lines + ) + lines = [''.join((lpad, pad.join(line), rpad)) for line in lines] + return lines + + +def _get_column_widths(dataset, max_table_width=MAX_TABLE_WIDTH, pad_len=3): + """ + Returns a list of column widths proportional to the median length + of the text in their cells. + """ + str_lens, word_lens = _get_column_string_lengths(dataset) + median_lens = [int(median(lens)) for lens in str_lens] + total = sum(median_lens) + if total > max_table_width - (pad_len * len(median_lens)): + column_widths = (max_table_width * l // total for l in median_lens) + else: + column_widths = (l for l in median_lens) + # Allow for separator and padding: + column_widths = (w - pad_len if w > pad_len else w for w in column_widths) + # Rather widen table than break words: + column_widths = [max(w, l) for w, l in zip(column_widths, word_lens)] + return column_widths + + +def export_set_as_simple_table(dataset, column_widths=None): + """ + Returns reStructuredText grid table representation of dataset. + """ + lines = [] + wrapper = TextWrapper() + if column_widths is None: + column_widths = _get_column_widths(dataset, pad_len=2) + border = ' '.join(['=' * w for w in column_widths]) + + lines.append(border) + if dataset.headers: + lines.extend(_row_to_lines( + dataset.headers, + column_widths, + wrapper, + sep='', + justify=JUSTIFY_CENTER, + )) + lines.append(border) + for row in dataset.dict: + values = iter(row.values() if hasattr(row, 'values') else row) + lines.extend(_row_to_lines(values, column_widths, wrapper, '')) + lines.append(border) + return '\n'.join(lines) + + +def export_set_as_grid_table(dataset, column_widths=None): + """ + Returns reStructuredText grid table representation of dataset. + + + >>> from tablib import Dataset + >>> from tablib.formats import rst + >>> bits = ((0, 0), (1, 0), (0, 1), (1, 1)) + >>> data = Dataset() + >>> data.headers = ['A', 'B', 'A and B'] + >>> for a, b in bits: + ... data.append([bool(a), bool(b), bool(a * b)]) + >>> print(rst.export_set(data, force_grid=True)) + +-------+-------+-------+ + | A | B | A and | + | | | B | + +=======+=======+=======+ + | False | False | False | + +-------+-------+-------+ + | True | False | False | + +-------+-------+-------+ + | False | True | False | + +-------+-------+-------+ + | True | True | True | + +-------+-------+-------+ + + """ + lines = [] + wrapper = TextWrapper() + if column_widths is None: + column_widths = _get_column_widths(dataset) + header_sep = '+=' + '=+='.join(['=' * w for w in column_widths]) + '=+' + row_sep = '+-' + '-+-'.join(['-' * w for w in column_widths]) + '-+' + + lines.append(row_sep) + if dataset.headers: + lines.extend(_row_to_lines( + dataset.headers, + column_widths, + wrapper, + justify=JUSTIFY_CENTER, + )) + lines.append(header_sep) + for row in dataset.dict: + values = iter(row.values() if hasattr(row, 'values') else row) + lines.extend(_row_to_lines(values, column_widths, wrapper)) + lines.append(row_sep) + return '\n'.join(lines) + + +def _use_simple_table(head0, col0, width0): + """ + Use a simple table if the text in the first column is never wrapped + + + >>> _use_simple_table('menu', ['egg', 'bacon'], 10) + True + >>> _use_simple_table(None, ['lobster thermidor', 'spam'], 10) + False + + """ + if head0 is not None: + head0 = to_unicode(head0) + if len(head0) > width0: + return False + for cell in col0: + cell = to_unicode(cell) + if len(cell) > width0: + return False + return True + + +def export_set(dataset, **kwargs): + """ + Returns reStructuredText table representation of dataset. + + Returns a simple table if the text in the first column is never + wrapped, otherwise returns a grid table. + + + >>> from tablib import Dataset + >>> bits = ((0, 0), (1, 0), (0, 1), (1, 1)) + >>> data = Dataset() + >>> data.headers = ['A', 'B', 'A and B'] + >>> for a, b in bits: + ... data.append([bool(a), bool(b), bool(a * b)]) + >>> table = data.rst + >>> table.split('\\n') == [ + ... '===== ===== =====', + ... ' A B A and', + ... ' B ', + ... '===== ===== =====', + ... 'False False False', + ... 'True False False', + ... 'False True False', + ... 'True True True ', + ... '===== ===== =====', + ... ] + True + + """ + if not dataset.dict: + return '' + force_grid = kwargs.get('force_grid', False) + max_table_width = kwargs.get('max_table_width', MAX_TABLE_WIDTH) + column_widths = _get_column_widths(dataset, max_table_width) + + use_simple_table = _use_simple_table( + dataset.headers[0] if dataset.headers else None, + dataset.get_col(0), + column_widths[0], + ) + if use_simple_table and not force_grid: + return export_set_as_simple_table(dataset, column_widths) + else: + return export_set_as_grid_table(dataset, column_widths) + + +def export_book(databook): + """ + reStructuredText representation of a Databook. + + Tables are separated by a blank line. All tables use the grid + format. + """ + return '\n\n'.join(export_set(dataset, force_grid=True) + for dataset in databook._datasets) diff --git a/tablib/packages/statistics.py b/tablib/packages/statistics.py new file mode 100644 index 0000000..e97a6c9 --- /dev/null +++ b/tablib/packages/statistics.py @@ -0,0 +1,24 @@ +from __future__ import division + + +def median(data): + """ + Return the median (middle value) of numeric data, using the common + "mean of middle two" method. If data is empty, ValueError is raised. + + Mimics the behaviour of Python3's statistics.median + + >>> median([1, 3, 5]) + 3 + >>> median([1, 3, 5, 7]) + 4.0 + + """ + data = sorted(data) + n = len(data) + if not n: + raise ValueError("No median for empty data") + i = n // 2 + if n % 2: + return data[i] + return (data[i - 1] + data[i]) / 2 -- cgit v1.2.1 From 3d5943a8a43a721bea1f3e33dc669a581a095c53 Mon Sep 17 00:00:00 2001 From: Bruno Soares Date: Wed, 12 Sep 2018 15:49:46 -0300 Subject: Fix: Circular reference detected error (#332) * Rename function name * Add uuid handler on json dumps * Add myself to authors --- tablib/formats/_json.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'tablib') diff --git a/tablib/formats/_json.py b/tablib/formats/_json.py index a3d6cc3..a3b88f8 100644 --- a/tablib/formats/_json.py +++ b/tablib/formats/_json.py @@ -3,6 +3,7 @@ """ Tablib - JSON Support """ import decimal +from uuid import UUID import tablib @@ -15,24 +16,23 @@ title = 'json' extensions = ('json', 'jsn') -def date_handler(obj): - if isinstance(obj, decimal.Decimal): +def serialize_objects_handler(obj): + if isinstance(obj, decimal.Decimal) or isinstance(obj, UUID): return str(obj) elif hasattr(obj, 'isoformat'): return obj.isoformat() else: return obj - # return obj.isoformat() if hasattr(obj, 'isoformat') else obj def export_set(dataset): """Returns JSON representation of Dataset.""" - return json.dumps(dataset.dict, default=date_handler) + return json.dumps(dataset.dict, default=serialize_objects_handler) def export_book(databook): """Returns JSON representation of Databook.""" - return json.dumps(databook._package(), default=date_handler) + return json.dumps(databook._package(), default=serialize_objects_handler) def import_set(dset, in_stream): -- cgit v1.2.1 From 4f8949417e5126f8416b61d7feb6cd666bae4fb9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20=22RooTer=22=20Urba=C5=84ski?= Date: Wed, 12 Sep 2018 21:15:20 +0200 Subject: ujson presence no longer breaks tablib (resolves #297) (#311) --- tablib/formats/_json.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) (limited to 'tablib') diff --git a/tablib/formats/_json.py b/tablib/formats/_json.py index a3b88f8..bbd2c96 100644 --- a/tablib/formats/_json.py +++ b/tablib/formats/_json.py @@ -3,14 +3,11 @@ """ Tablib - JSON Support """ import decimal +import json from uuid import UUID import tablib -try: - import ujson as json -except ImportError: - import json title = 'json' extensions = ('json', 'jsn') -- cgit v1.2.1 From d38549ef1e43c6c296b7f3d44210d61e34e6c4c9 Mon Sep 17 00:00:00 2001 From: lepuchi Date: Tue, 2 Oct 2018 23:26:19 +0530 Subject: only add row if it exists --- tablib/formats/_csv.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'tablib') diff --git a/tablib/formats/_csv.py b/tablib/formats/_csv.py index 994b23b..06e7830 100644 --- a/tablib/formats/_csv.py +++ b/tablib/formats/_csv.py @@ -44,7 +44,7 @@ def import_set(dset, in_stream, headers=True, **kwargs): if (i == 0) and (headers): dset.headers = row - else: + elif row: dset.append(row) -- cgit v1.2.1 From a28a057559bcfb39612e91af59f0e80a1796caf6 Mon Sep 17 00:00:00 2001 From: Tsuyoshi Hombashi Date: Sat, 6 Oct 2018 19:19:09 +0900 Subject: Replace a deprecated method call Workbook.remove_sheet method deprecated since openpyxl 2.4.0 --- tablib/formats/_xlsx.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'tablib') diff --git a/tablib/formats/_xlsx.py b/tablib/formats/_xlsx.py index eb921f9..1d29ba8 100644 --- a/tablib/formats/_xlsx.py +++ b/tablib/formats/_xlsx.py @@ -52,7 +52,7 @@ def export_book(databook, freeze_panes=True): wb = Workbook() for sheet in wb.worksheets: - wb.remove_sheet(sheet) + wb.remove(sheet) for i, dset in enumerate(databook._datasets): ws = wb.create_sheet() ws.title = dset.title if dset.title else 'Sheet%s' % (i) -- cgit v1.2.1 From dd2ba714d3eec5e6a14bdac27b4e6467b6dfb439 Mon Sep 17 00:00:00 2001 From: Jon Dufresne Date: Tue, 1 Jan 2019 09:58:53 -0800 Subject: Remove vendored ordereddict package Now that Python 2.6 support has been dropped, can remove the vendored ordereddict package. Use the stdlib collections.OrderedDict instead. --- tablib/compat.py | 7 --- tablib/core.py | 3 +- tablib/packages/ordereddict.py | 127 ----------------------------------------- 3 files changed, 2 insertions(+), 135 deletions(-) delete mode 100644 tablib/packages/ordereddict.py (limited to 'tablib') diff --git a/tablib/compat.py b/tablib/compat.py index d60ce72..916d6ab 100644 --- a/tablib/compat.py +++ b/tablib/compat.py @@ -13,13 +13,6 @@ import sys is_py3 = (sys.version_info[0] > 2) - -try: - from collections import OrderedDict -except ImportError: - from tablib.packages.ordereddict import OrderedDict - - if is_py3: from io import BytesIO from itertools import zip_longest as izip_longest diff --git a/tablib/core.py b/tablib/core.py index 8c49f2e..f9951a9 100644 --- a/tablib/core.py +++ b/tablib/core.py @@ -9,12 +9,13 @@ :license: MIT, see LICENSE for more details. """ +from collections import OrderedDict from copy import copy from operator import itemgetter from tablib import formats -from tablib.compat import OrderedDict, unicode +from tablib.compat import unicode __title__ = 'tablib' diff --git a/tablib/packages/ordereddict.py b/tablib/packages/ordereddict.py deleted file mode 100644 index a5b896d..0000000 --- a/tablib/packages/ordereddict.py +++ /dev/null @@ -1,127 +0,0 @@ -# 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 = next(reversed(self)) - else: - key = next(iter(self)) - 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__, list(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(list(self.items()), list(other.items())): - if p != q: - return False - return True - return dict.__eq__(self, other) - - def __ne__(self, other): - return not self == other -- cgit v1.2.1 From 499ce52304b8994500ba0e81bd23d9ac6a2038cf Mon Sep 17 00:00:00 2001 From: Jon Dufresne Date: Tue, 1 Jan 2019 10:40:29 -0800 Subject: Remove unused compat entries Organize both the Python2 & Python3 sections in the same order so they are easier to compare. Removed: - basestring - ifilter - bytes --- tablib/compat.py | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) (limited to 'tablib') diff --git a/tablib/compat.py b/tablib/compat.py index d60ce72..2f97650 100644 --- a/tablib/compat.py +++ b/tablib/compat.py @@ -22,19 +22,14 @@ except ImportError: if is_py3: from io import BytesIO - from itertools import zip_longest as izip_longest - from statistics import median + from io import StringIO from tablib.packages import markup3 as markup - import tablib.packages.dbfpy3 as dbfpy - + from statistics import median + from itertools import zip_longest as izip_longest import csv - from io import StringIO - # py3 mappings + import tablib.packages.dbfpy3 as dbfpy - ifilter = filter unicode = str - bytes = bytes - basestring = str xrange = range else: @@ -42,8 +37,7 @@ else: from cStringIO import StringIO from tablib.packages import markup from tablib.packages.statistics import median - from itertools import ifilter, izip_longest - + from itertools import izip_longest import unicodecsv as csv import tablib.packages.dbfpy as dbfpy -- cgit v1.2.1 From 102073c426d7a76384e75379abbf1a3b0ed78662 Mon Sep 17 00:00:00 2001 From: Mathias Loesch Date: Wed, 23 Jan 2019 14:18:01 +0100 Subject: Add Jira table export --- tablib/core.py | 8 +++++++- tablib/formats/__init__.py | 3 ++- tablib/formats/_jira.py | 39 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 48 insertions(+), 2 deletions(-) create mode 100644 tablib/formats/_jira.py (limited to 'tablib') diff --git a/tablib/core.py b/tablib/core.py index 8c49f2e..3dc23ea 100644 --- a/tablib/core.py +++ b/tablib/core.py @@ -631,7 +631,6 @@ class Dataset(object): """ pass - @property def latex(): """A LaTeX booktabs representation of the :class:`Dataset` object. If a @@ -641,6 +640,13 @@ class Dataset(object): """ pass + @property + def jira(): + """A Jira table representation of the :class:`Dataset` object. + + .. note:: This method can be used for export only. + """ + pass # ---- # Rows diff --git a/tablib/formats/__init__.py b/tablib/formats/__init__.py index 3eb747e..418e607 100644 --- a/tablib/formats/__init__.py +++ b/tablib/formats/__init__.py @@ -15,5 +15,6 @@ from . import _dbf as dbf from . import _latex as latex from . import _df as df from . import _rst as rst +from . import _jira as jira -available = (json, xls, yaml, csv, dbf, tsv, html, latex, xlsx, ods, df, rst) +available = (json, xls, yaml, csv, dbf, tsv, html, jira, latex, xlsx, ods, df, rst) diff --git a/tablib/formats/_jira.py b/tablib/formats/_jira.py new file mode 100644 index 0000000..55fce52 --- /dev/null +++ b/tablib/formats/_jira.py @@ -0,0 +1,39 @@ +# -*- coding: utf-8 -*- + +"""Tablib - Jira table export support. + + Generates a Jira table from the dataset. +""" +from tablib.compat import unicode + +title = 'jira' + + +def export_set(dataset): + """Formats the dataset according to the Jira table syntax: + + ||heading 1||heading 2||heading 3|| + |col A1|col A2|col A3| + |col B1|col B2|col B3| + + :param dataset: dataset to serialize + :type dataset: tablib.core.Dataset + """ + + header = _get_header(dataset.headers) if dataset.headers else '' + body = _get_body(dataset) + return '%s\n%s' % (header, body) if header else body + + +def _get_body(dataset): + return '\n'.join([_serialize_row(row) for row in dataset]) + + +def _get_header(headers): + return _serialize_row(headers, delimiter='||') + + +def _serialize_row(row, delimiter='|'): + return '%s%s%s' % (delimiter, + delimiter.join([unicode(item) if item else ' ' for item in row]), + delimiter) -- cgit v1.2.1