Compare commits

...

7 Commits

Author SHA1 Message Date
Claude Paroz 985c3d98b0 Set the release date for 2.0.0 2020-05-16 14:04:19 +02:00
Claude Paroz 6d097c0214 Fixes #465 - Allow importing 'ragged' .xlsx files (#466) 2020-05-16 09:07:32 +03:00
dragonworks 16b5565354 Fixes #462 - Update xlsx import to read cell values instead of cell formulas
Co-authored-by: Claude Paroz <claude@2xlibre.net>
2020-03-11 09:05:43 +01:00
Claude Paroz c25fe54b6f Refs #373 - Import dates from xls files as Python datetime objects 2020-03-09 17:05:32 +01:00
Tim Gates b39aefb8d8 Fix simple typo: belonogs -> belongs (#460)
Closes #459
2020-02-21 10:26:58 +02:00
Claude Paroz a442758729 Fixes #457 - Bumped openpyxl dependency to 2.6.0 (#458) 2020-02-16 15:05:20 +02:00
Claude Paroz 21479001a7 Fixes #453 - Reversing behavior of Row.lpush/Row.rpush (#454)
Co-authored-by: chim <chenpan@xiaomai5.com>
2020-02-13 20:51:49 +02:00
12 changed files with 70 additions and 25 deletions
+18
View File
@@ -1,5 +1,23 @@
# History # History
## 2.0.0 (2020-05-16)
### Breaking changes
- The `Row.lpush/rpush` logic was reversed. `lpush` was appending while `rpush`
and `append` were prepending. This was fixed (reversed behavior). If you
counted on the broken behavior, please update your code (#453).
### Bugfixes
- Fixed minimal openpyxl dependency version to 2.6.0 (#457).
- Dates from xls files are now read as Python datetime objects (#373).
- Allow import of "ragged" xlsx files (#465).
### Improvements
- When importing an xlsx file, Tablib will now read cell values instead of formulas (#462).
## 1.1.0 (2020-02-13) ## 1.1.0 (2020-02-13)
### Deprecations ### Deprecations
+9
View File
@@ -206,6 +206,15 @@ Import/export data in Excel 07+ Spreadsheet representation.
This format is optional, install Tablib with ``pip install tablib[xlsx]`` to This format is optional, install Tablib with ``pip install tablib[xlsx]`` to
make the format available. make the format available.
.. note::
When reading an ``xlsx`` file containing formulas in its cells, Tablib will
read the cell values, not the cell formulas.
.. versionchanged:: 2.0.0
Reads cell values instead of formulas.
.. admonition:: Binary Warning .. admonition:: Binary Warning
The ``xlsx`` file format is binary, so make sure to write in binary mode:: The ``xlsx`` file format is binary, so make sure to write in binary mode::
+2 -2
View File
@@ -38,13 +38,13 @@ setup(
], ],
python_requires='>=3.5', python_requires='>=3.5',
extras_require={ extras_require={
'all': ['markuppy', 'odfpy', 'openpyxl>=2.4.0', 'pandas', 'pyyaml', 'tabulate', 'xlrd', 'xlwt'], 'all': ['markuppy', 'odfpy', 'openpyxl>=2.6.0', 'pandas', 'pyyaml', 'tabulate', 'xlrd', 'xlwt'],
'cli': ['tabulate'], 'cli': ['tabulate'],
'html': ['markuppy'], 'html': ['markuppy'],
'ods': ['odfpy'], 'ods': ['odfpy'],
'pandas': ['pandas'], 'pandas': ['pandas'],
'xls': ['xlrd', 'xlwt'], 'xls': ['xlrd', 'xlwt'],
'xlsx': ['openpyxl>=2.4.0'], 'xlsx': ['openpyxl>=2.6.0'],
'yaml': ['pyyaml'], 'yaml': ['pyyaml'],
}, },
) )
+2 -2
View File
@@ -71,10 +71,10 @@ class Row:
setattr(self, k, v) setattr(self, k, v)
def rpush(self, value): def rpush(self, value):
self.insert(0, value) self.insert(len(self._row), value)
def lpush(self, value): def lpush(self, value):
self.insert(len(value), value) self.insert(0, value)
def append(self, value): def append(self, value):
self.rpush(value) self.rpush(value)
+9 -1
View File
@@ -6,6 +6,7 @@ from io import BytesIO
import tablib import tablib
import xlrd import xlrd
import xlwt import xlwt
from xlrd.xldate import xldate_as_datetime
# special styles # special styles
wrap = xlwt.easyxf("alignment: wrap on") wrap = xlwt.easyxf("alignment: wrap on")
@@ -74,12 +75,19 @@ class XLSFormat:
dset.title = sheet.name dset.title = sheet.name
def cell_value(value, type_):
if type_ == xlrd.XL_CELL_ERROR:
return xlrd.error_text_from_code[value]
elif type_ == xlrd.XL_CELL_DATE:
return xldate_as_datetime(value, xls_book.datemode)
return value
for i in range(sheet.nrows): for i in range(sheet.nrows):
if i == 0 and headers: if i == 0 and headers:
dset.headers = sheet.row_values(0) dset.headers = sheet.row_values(0)
else: else:
dset.append([ dset.append([
val if typ != xlrd.XL_CELL_ERROR else xlrd.error_text_from_code[val] cell_value(val, typ)
for val, typ in zip(sheet.row_values(i), sheet.row_types(i)) for val, typ in zip(sheet.row_values(i), sheet.row_types(i))
]) ])
+4 -2
View File
@@ -63,7 +63,7 @@ class XLSXFormat:
dset.wipe() dset.wipe()
xls_book = load_workbook(in_stream, read_only=True) xls_book = load_workbook(in_stream, read_only=True, data_only=True)
sheet = xls_book.active sheet = xls_book.active
dset.title = sheet.title dset.title = sheet.title
@@ -81,7 +81,7 @@ class XLSXFormat:
dbook.wipe() dbook.wipe()
xls_book = load_workbook(in_stream, read_only=True) xls_book = load_workbook(in_stream, read_only=True, data_only=True)
for sheet in xls_book.worksheets: for sheet in xls_book.worksheets:
data = tablib.Dataset() data = tablib.Dataset()
@@ -92,6 +92,8 @@ class XLSXFormat:
if (i == 0) and (headers): if (i == 0) and (headers):
data.headers = row_vals data.headers = row_vals
else: else:
if i > 0 and len(row_vals) < data.width:
row_vals += [''] * (data.width - len(row_vals))
data.append(row_vals) data.append(row_vals)
dbook.add_sheet(data) dbook.add_sheet(data)
+1 -1
View File
@@ -60,7 +60,7 @@ class DbfRecord:
Arguments: Arguments:
dbf: dbf:
A `Dbf.Dbf` instance this record belonogs to. A `Dbf.Dbf` instance this record belongs to.
index: index:
An integer record index or None. If this value is An integer record index or None. If this value is
None, record will be appended to the DBF. None, record will be appended to the DBF.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+1 -1
View File
@@ -2,7 +2,7 @@ pytest
pytest-cov pytest-cov
MarkupPy MarkupPy
odfpy odfpy
openpyxl>=2.4.0 openpyxl>=2.6.0
pandas pandas
pyyaml pyyaml
tabulate tabulate
+24 -16
View File
@@ -556,27 +556,15 @@ class TablibTestCase(BaseTestCase):
def test_row_lpush(self): def test_row_lpush(self):
"""Row lpush.""" """Row lpush."""
# Arrange
john = Row(self.john) john = Row(self.john)
george = Row(self.george) john.lpush(53)
self.assertEqual(john.list, [53, 'John', 'Adams', 90])
# Act
john.lpush(george)
# Assert
self.assertEqual(john[-1], george)
def test_row_append(self): def test_row_append(self):
"""Row append.""" """Row append."""
# Arrange
john = Row(self.john) john = Row(self.john)
george = Row(self.george) john.append('stuff')
self.assertEqual(john.list, ['John', 'Adams', 90, 'stuff'])
# Act
john.append(george)
# Assert
self.assertEqual(john[0], george)
def test_row_contains(self): def test_row_contains(self):
"""Row __contains__.""" """Row __contains__."""
@@ -987,6 +975,12 @@ class XLSTests(BaseTestCase):
in_stream = self.founders.xls in_stream = self.founders.xls
self.assertEqual(detect_format(in_stream), 'xls') self.assertEqual(detect_format(in_stream), 'xls')
def test_xls_date_import(self):
xls_source = Path(__file__).parent / 'files' / 'dates.xls'
with open(str(xls_source), mode='rb') as fh:
dset = tablib.Dataset().load(fh, 'xls')
self.assertEqual(dset.dict[0]['birth_date'], datetime.datetime(2015, 4, 12, 0, 0))
def test_xls_import_with_errors(self): def test_xls_import_with_errors(self):
"""Errors from imported files are kept as errors.""" """Errors from imported files are kept as errors."""
xls_source = Path(__file__).parent / 'files' / 'errors.xls' xls_source = Path(__file__).parent / 'files' / 'errors.xls'
@@ -1021,6 +1015,13 @@ class XLSXTests(BaseTestCase):
self.assertEqual(data.dict[0]['float'], 21.55) self.assertEqual(data.dict[0]['float'], 21.55)
self.assertEqual(data.dict[0]['date/time'], date_time) self.assertEqual(data.dict[0]['date/time'], date_time)
def test_xlsx_import_set_ragged(self):
"""Import XLSX file when not all rows have the same length."""
xlsx_source = Path(__file__).parent / 'files' / 'ragged.xlsx'
with open(str(xlsx_source), mode='rb') as fh:
book = tablib.Databook().load(fh, 'xlsx')
self.assertEqual(book.sheets()[0].pop(), (1.0, ''))
def test_xlsx_wrong_char(self): def test_xlsx_wrong_char(self):
"""Bad characters are not silently ignored. We let the exception bubble up.""" """Bad characters are not silently ignored. We let the exception bubble up."""
from openpyxl.utils.exceptions import IllegalCharacterError from openpyxl.utils.exceptions import IllegalCharacterError
@@ -1029,6 +1030,13 @@ class XLSXTests(BaseTestCase):
data.append(('string', b'\x0cf')) data.append(('string', b'\x0cf'))
data.xlsx data.xlsx
def test_xlsx_cell_values(self):
"""Test cell values are read and not formulas"""
xls_source = Path(__file__).parent / 'files' / 'xlsx_cell_values.xlsx'
with xls_source.open('rb') as fh:
data = tablib.Dataset().load(fh)
self.assertEqual(data.headers[0], 'Hello World')
class JSONTests(BaseTestCase): class JSONTests(BaseTestCase):
def test_json_format_detect(self): def test_json_format_detect(self):