From 660990b6b06beb3972a6ecc0879029914e957161 Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Thu, 7 Nov 2019 22:37:49 +0100 Subject: [PATCH] Fixes #440 -Normalize stream inputs as IO streams --- HISTORY.md | 3 +++ docs/tutorial.rst | 9 +++++--- src/tablib/core.py | 37 ++++++++++++++++++++++----------- src/tablib/formats/__init__.py | 5 +++-- src/tablib/formats/_csv.py | 4 ++-- src/tablib/formats/_dbf.py | 7 ++----- src/tablib/formats/_df.py | 4 +++- src/tablib/formats/_json.py | 6 +++--- src/tablib/formats/_xls.py | 2 +- src/tablib/formats/_xlsx.py | 7 ++----- src/tablib/utils.py | 13 ++++++++++++ tests/files/founders.xlsx | Bin 0 -> 4873 bytes tests/test_tablib.py | 30 +++++++++++++++++++------- 13 files changed, 86 insertions(+), 41 deletions(-) create mode 100644 src/tablib/utils.py create mode 100644 tests/files/founders.xlsx diff --git a/HISTORY.md b/HISTORY.md index f3a1a34c..945608ca 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -12,6 +12,9 @@ - Formats can now be dynamically registered through the `tablib.formats.registry.refister` API (#256). +- Tablib methods expecting data input (`detect_format`, `import_set`, + `Dataset.load`, `Databook.load`) now accepts file-like objects in addition + to raw strings and bytestrings (#440). ### Bugfixes diff --git a/docs/tutorial.rst b/docs/tutorial.rst index 6226aef7..23f48280 100644 --- a/docs/tutorial.rst +++ b/docs/tutorial.rst @@ -106,7 +106,8 @@ Importing Data -------------- Creating a :class:`tablib.Dataset` object by importing a pre-existing file is simple. :: - imported_data = Dataset().load(open('data.csv').read()) + with open('data.csv', 'r') as fh: + imported_data = Dataset().load(fh) This detects what sort of data is being passed in, and uses an appropriate formatter to do the import. So you can import from a variety of different file types. @@ -114,7 +115,8 @@ This detects what sort of data is being passed in, and uses an appropriate forma When the format is :class:`csv `, :class:`tsv `, :class:`dbf `, :class:`xls ` or :class:`xlsx `, and the data source does not have headers, the import should be done as follows :: - imported_data = Dataset().load(open('data.csv').read(), headers=False) + with open('data.csv', 'r') as fh: + imported_data = Dataset().load(fh, headers=False) -------------- Exporting Data @@ -320,7 +322,8 @@ Open an Excel Workbook and read first sheet Open an Excel 2007 and later workbook with a single sheet (or a workbook with multiple sheets but you just want the first sheet). :: data = tablib.Dataset() - data.xlsx = open('my_excel_file.xlsx', 'rb').read() + with open('my_excel_file.xlsx', 'rb') as fh: + data.load(fh, 'xlsx') print(data) Excel Workbook With Multiple Sheets diff --git a/src/tablib/core.py b/src/tablib/core.py index 9eaeb61e..ef3c26e2 100644 --- a/src/tablib/core.py +++ b/src/tablib/core.py @@ -21,6 +21,7 @@ UnsupportedFormat, ) from tablib.formats import registry +from tablib.utils import normalize_input __title__ = 'tablib' __author__ = 'Kenneth Reitz' @@ -239,8 +240,9 @@ def __str__(self): def _get_in_format(self, fmt_key, **kwargs): return registry.get_format(fmt_key).export_set(self, **kwargs) - def _set_in_format(self, fmt_key, *args, **kwargs): - return registry.get_format(fmt_key).import_set(self, *args, **kwargs) + def _set_in_format(self, fmt_key, in_stream, **kwargs): + in_stream = normalize_input(in_stream) + return registry.get_format(fmt_key).import_set(self, in_stream, **kwargs) def _validate(self, row=None, col=None, safety=False): """Assures size of every row in dataset is of proper proportions.""" @@ -402,12 +404,14 @@ def width(self): def load(self, in_stream, format=None, **kwargs): """ Import `in_stream` to the :class:`Dataset` object using the `format`. + `in_stream` can be a file-like object, a string, or a bytestring. :param \\*\\*kwargs: (optional) custom configuration to the format `import_set`. """ + stream = normalize_input(in_stream) if not format: - format = detect_format(in_stream) + format = detect_format(stream) fmt = registry.get_format(format) if not hasattr(fmt, 'import_set'): @@ -416,7 +420,7 @@ def load(self, in_stream, format=None, **kwargs): if not import_set: raise UnsupportedFormat('Format {} cannot be imported.'.format(format)) - fmt.import_set(self, in_stream, **kwargs) + fmt.import_set(self, stream, **kwargs) return self def export(self, format, **kwargs): @@ -861,18 +865,20 @@ def size(self): def load(self, in_stream, format, **kwargs): """ Import `in_stream` to the :class:`Databook` object using the `format`. + `in_stream` can be a file-like object, a string, or a bytestring. :param \\*\\*kwargs: (optional) custom configuration to the format `import_book`. """ + stream = normalize_input(in_stream) if not format: - format = detect_format(in_stream) + format = detect_format(stream) fmt = registry.get_format(format) if not hasattr(fmt, 'import_book'): raise UnsupportedFormat('Format {} cannot be loaded.'.format(format)) - fmt.import_book(self, in_stream, **kwargs) + fmt.import_book(self, stream, **kwargs) return self def export(self, format, **kwargs): @@ -889,25 +895,32 @@ def export(self, format, **kwargs): def detect_format(stream): - """Return format name of given stream.""" + """Return format name of given stream (file-like object, string, or bytestring).""" + stream = normalize_input(stream) + fmt_title = None for fmt in registry.formats(): try: if fmt.detect(stream): - return fmt.title + fmt_title = fmt.title + break except AttributeError: pass + finally: + if hasattr(stream, 'seek'): + stream.seek(0) + return fmt_title def import_set(stream, format=None, **kwargs): - """Return dataset of given stream.""" + """Return dataset of given stream (file-like object, string, or bytestring).""" - return Dataset().load(stream, format, **kwargs) + return Dataset().load(normalize_input(stream), format, **kwargs) def import_book(stream, format=None, **kwargs): - """Return dataset of given stream.""" + """Return dataset of given stream (file-like object, string, or bytestring).""" - return Databook().load(stream, format, **kwargs) + return Databook().load(normalize_input(stream), format, **kwargs) registry.register_builtins() diff --git a/src/tablib/formats/__init__.py b/src/tablib/formats/__init__.py index 1b5b0d69..848e6652 100644 --- a/src/tablib/formats/__init__.py +++ b/src/tablib/formats/__init__.py @@ -6,6 +6,7 @@ from importlib.util import find_spec from tablib.exceptions import UnsupportedFormat +from tablib.utils import normalize_input from ._csv import CSVFormat from ._json import JSONFormat @@ -52,7 +53,7 @@ def __get__(self, obj, cls, **kwargs): def __set__(self, obj, val): self.ensure_format_loaded() - return self._format.import_book(obj, val) + return self._format.import_book(obj, normalize_input(val)) class ImportExportSetDescriptor(FormatDescriptorBase): @@ -62,7 +63,7 @@ def __get__(self, obj, cls, **kwargs): def __set__(self, obj, val): self.ensure_format_loaded() - return self._format.import_set(obj, val) + return self._format.import_set(obj, normalize_input(val)) class Registry: diff --git a/src/tablib/formats/_csv.py b/src/tablib/formats/_csv.py index cb209fc4..14d7bb27 100644 --- a/src/tablib/formats/_csv.py +++ b/src/tablib/formats/_csv.py @@ -40,7 +40,7 @@ def import_set(cls, dset, in_stream, headers=True, **kwargs): kwargs.setdefault('delimiter', cls.DEFAULT_DELIMITER) - rows = csv.reader(StringIO(in_stream), **kwargs) + rows = csv.reader(in_stream, **kwargs) for i, row in enumerate(rows): if (i == 0) and (headers): @@ -52,7 +52,7 @@ def import_set(cls, dset, in_stream, headers=True, **kwargs): def detect(cls, stream, delimiter=None): """Returns True if given stream is valid CSV.""" try: - csv.Sniffer().sniff(stream[:1024], delimiters=delimiter or cls.DEFAULT_DELIMITER) + csv.Sniffer().sniff(stream.read(1024), delimiters=delimiter or cls.DEFAULT_DELIMITER) return True except Exception: return False diff --git a/src/tablib/formats/_dbf.py b/src/tablib/formats/_dbf.py index 4ff16048..7898cbd6 100644 --- a/src/tablib/formats/_dbf.py +++ b/src/tablib/formats/_dbf.py @@ -50,7 +50,7 @@ def import_set(cls, dset, in_stream, headers=True): """Returns a dataset from a DBF stream.""" dset.wipe() - _dbf = dbf.Dbf(io.BytesIO(in_stream)) + _dbf = dbf.Dbf(in_stream) dset.headers = _dbf.fieldNames for record in range(_dbf.recordCount): row = [_dbf[record][f] for f in _dbf.fieldNames] @@ -59,11 +59,8 @@ def import_set(cls, dset, in_stream, headers=True): @classmethod def detect(cls, stream): """Returns True if the given stream is valid DBF""" - #_dbf = dbf.Table(StringIO(stream)) try: - if type(stream) is not bytes: - stream = bytes(stream, 'utf-8') - _dbf = dbf.Dbf(io.BytesIO(stream), readOnly=True) + _dbf = dbf.Dbf(stream, readOnly=True) return True except Exception: return False diff --git a/src/tablib/formats/_df.py b/src/tablib/formats/_df.py index b4cfa118..d8bf877c 100644 --- a/src/tablib/formats/_df.py +++ b/src/tablib/formats/_df.py @@ -16,8 +16,10 @@ def detect(cls, stream): """Returns True if given stream is a DataFrame.""" if DataFrame is None: return False + elif isinstance(stream, DataFrame): + return True try: - DataFrame(stream) + DataFrame(stream.read()) return True except ValueError: return False diff --git a/src/tablib/formats/_json.py b/src/tablib/formats/_json.py index 99e2aafe..dd8c3795 100644 --- a/src/tablib/formats/_json.py +++ b/src/tablib/formats/_json.py @@ -35,14 +35,14 @@ def import_set(cls, dset, in_stream): """Returns dataset from JSON stream.""" dset.wipe() - dset.dict = json.loads(in_stream) + dset.dict = json.load(in_stream) @classmethod def import_book(cls, dbook, in_stream): """Returns databook from JSON stream.""" dbook.wipe() - for sheet in json.loads(in_stream): + for sheet in json.load(in_stream): data = tablib.Dataset() data.title = sheet['title'] data.dict = sheet['data'] @@ -52,7 +52,7 @@ def import_book(cls, dbook, in_stream): def detect(cls, stream): """Returns True if given stream is valid JSON.""" try: - json.loads(stream) + json.load(stream) return True except (TypeError, ValueError): return False diff --git a/src/tablib/formats/_xls.py b/src/tablib/formats/_xls.py index fd39b46b..0b13d279 100644 --- a/src/tablib/formats/_xls.py +++ b/src/tablib/formats/_xls.py @@ -70,7 +70,7 @@ def import_set(cls, dset, in_stream, headers=True): dset.wipe() - xls_book = xlrd.open_workbook(file_contents=in_stream) + xls_book = xlrd.open_workbook(file_contents=in_stream.read()) sheet = xls_book.sheet_by_index(0) dset.title = sheet.name diff --git a/src/tablib/formats/_xlsx.py b/src/tablib/formats/_xlsx.py index cc0a6106..d7416b34 100644 --- a/src/tablib/formats/_xlsx.py +++ b/src/tablib/formats/_xlsx.py @@ -18,9 +18,6 @@ class XLSXFormat: @classmethod def detect(cls, stream): """Returns True if given stream is a readable excel file.""" - if isinstance(stream, bytes): - # load_workbook expects a file-like object. - stream = BytesIO(stream) try: openpyxl.reader.excel.load_workbook(stream, read_only=True) return True @@ -63,7 +60,7 @@ def import_set(cls, dset, in_stream, headers=True): dset.wipe() - xls_book = openpyxl.reader.excel.load_workbook(BytesIO(in_stream), read_only=True) + xls_book = openpyxl.reader.excel.load_workbook(in_stream, read_only=True) sheet = xls_book.active dset.title = sheet.title @@ -81,7 +78,7 @@ def import_book(cls, dbook, in_stream, headers=True): dbook.wipe() - xls_book = openpyxl.reader.excel.load_workbook(BytesIO(in_stream), read_only=True) + xls_book = openpyxl.reader.excel.load_workbook(in_stream, read_only=True) for sheet in xls_book.worksheets: data = tablib.Dataset() diff --git a/src/tablib/utils.py b/src/tablib/utils.py new file mode 100644 index 00000000..39de8ce4 --- /dev/null +++ b/src/tablib/utils.py @@ -0,0 +1,13 @@ +from io import BytesIO, StringIO + + +def normalize_input(stream): + """ + Accept either a str/bytes stream or a file-like object and always return a + file-like object. + """ + if isinstance(stream, str): + return StringIO(stream) + elif isinstance(stream, bytes): + return BytesIO(stream) + return stream diff --git a/tests/files/founders.xlsx b/tests/files/founders.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..bd6e41e3c0d736480867cf6b59ceb26df2925a5f GIT binary patch literal 4873 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 literal 0 HcmV?d00001 diff --git a/tests/test_tablib.py b/tests/test_tablib.py index 91df57fb..e71105e6 100755 --- a/tests/test_tablib.py +++ b/tests/test_tablib.py @@ -7,6 +7,7 @@ import pickle import unittest from collections import OrderedDict +from io import BytesIO, StringIO from pathlib import Path from uuid import uuid4 @@ -302,6 +303,18 @@ def test_book_unsupported_export(self): with self.assertRaises(UnsupportedFormat): book.export('csv') + def test_book_import_from_file(self): + xlsx_source = Path(__file__).parent / 'files' / 'founders.xlsx' + with open(str(xlsx_source), mode='rb') as fh: + book = tablib.Databook().load(fh, 'xlsx') + self.assertEqual(eval(book.json)[0]['title'], 'Feuille1') + + def test_dataset_import_from_file(self): + xlsx_source = Path(__file__).parent / 'files' / 'founders.xlsx' + with open(str(xlsx_source), mode='rb') as fh: + dset = tablib.Dataset().load(fh, 'xlsx') + self.assertEqual(eval(dset.json)[0]['last_name'], 'Adams') + def test_auto_format_detect(self): """Test auto format detection.""" # html, jira, latex, rst are export only. @@ -330,7 +343,9 @@ def test_auto_format_detect(self): _tsv = '1\t2\t3\n4\t5\t6\n7\t8\t9\n' self.assertEqual(tablib.detect_format(_tsv), 'tsv') - _bunk = '¡¡¡¡¡¡---///\n\n\n¡¡£™∞¢£§∞§¶•¶ª∞¶•ªº••ª–º§•†•§º¶•†¥ª–º•§ƒø¥¨©πƒø†ˆ¥ç©¨√øˆ¥≈†ƒ¥ç©ø¨çˆ¥ƒçø¶' + _bunk = StringIO( + '¡¡¡¡¡¡---///\n\n\n¡¡£™∞¢£§∞§¶•¶ª∞¶•ªº••ª–º§•†•§º¶•†¥ª–º•§ƒø¥¨©πƒø†ˆ¥ç©¨√øˆ¥≈†ƒ¥ç©ø¨çˆ¥ƒçø¶' + ) self.assertEqual(tablib.detect_format(_bunk), None) def test_transpose(self): @@ -692,12 +707,12 @@ class CSVTests(BaseTestCase): def test_csv_format_detect(self): """Test CSV format detection.""" - _csv = ( + _csv = StringIO( '1,2,3\n' '4,5,6\n' '7,8,9\n' ) - _bunk = ( + _bunk = StringIO( '¡¡¡¡¡¡¡¡£™∞¢£§∞§¶•¶ª∞¶•ªº••ª–º§•†•§º¶•†¥ª–º•§ƒø¥¨©πƒø†ˆ¥ç©¨√øˆ¥≈†ƒ¥ç©ø¨çˆ¥ƒçø¶' ) @@ -915,12 +930,12 @@ def test_tsv_import_set(self): def test_tsv_format_detect(self): """Test TSV format detection.""" - _tsv = ( + _tsv = StringIO( '1\t2\t3\n' '4\t5\t6\n' '7\t8\t9\n' ) - _bunk = ( + _bunk = StringIO( '¡¡¡¡¡¡¡¡£™∞¢£§∞§¶•¶ª∞¶•ªº••ª–º§•†•§º¶•†¥ª–º•§ƒø¥¨©πƒø†ˆ¥ç©¨√øˆ¥≈†ƒ¥ç©ø¨çˆ¥ƒçø¶' ) @@ -999,8 +1014,8 @@ class JSONTests(BaseTestCase): def test_json_format_detect(self): """Test JSON format detection.""" - _json = '[{"last_name": "Adams","age": 90,"first_name": "John"}]' - _bunk = ( + _json = StringIO('[{"last_name": "Adams","age": 90,"first_name": "John"}]') + _bunk = StringIO( '¡¡¡¡¡¡¡¡£™∞¢£§∞§¶•¶ª∞¶•ªº••ª–º§•†•§º¶•†¥ª–º•§ƒø¥¨©πƒø†ˆ¥ç©¨√øˆ¥≈†ƒ¥ç©ø¨çˆ¥ƒçø¶' ) @@ -1251,6 +1266,7 @@ def test_dbf_format_detect(self): _dbf += b' Jefferson' + (b' ' * 70) _dbf += b' 50.0000000' _dbf += b'\x1a' + _dbf = BytesIO(_dbf) _yaml = '- {age: 90, first_name: John, last_name: Adams}' _tsv = 'foo\tbar'