diff --git a/docs/conf.py b/docs/conf.py index 5c56c663..8df446ed 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # Tablib documentation build configuration file, created by # sphinx-quickstart on Tue Oct 5 15:25:21 2010. @@ -38,8 +37,8 @@ master_doc = 'index' # General information about the project. -project = u'Tablib' -copyright = u'2019 Jazzband' +project = 'Tablib' +copyright = '2019 Jazzband' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the @@ -181,8 +180,8 @@ # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass [howto/manual]). latex_documents = [ - ('index', 'Tablib.tex', u'Tablib Documentation', - u'Jazzband', 'manual'), + ('index', 'Tablib.tex', 'Tablib Documentation', + 'Jazzband', 'manual'), ] latex_use_modindex = False @@ -222,6 +221,6 @@ # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ - ('index', 'tablib', u'Tablib Documentation', - [u'Jazzband'], 1) + ('index', 'tablib', 'Tablib Documentation', + ['Jazzband'], 1) ] diff --git a/docs/intro.rst b/docs/intro.rst index 00562c1e..064e5adb 100644 --- a/docs/intro.rst +++ b/docs/intro.rst @@ -74,7 +74,7 @@ THE SOFTWARE. Pythons Supported ----------------- -Python 3.5+ are officially supported. +Python 3.5+ is officially supported. Now, go :ref:`install Tablib `. diff --git a/pytest.ini b/pytest.ini index f69d3367..22886516 100644 --- a/pytest.ini +++ b/pytest.ini @@ -1,4 +1,4 @@ [pytest] norecursedirs = .git .* -addopts = -rsxX --showlocals --tb=native --cov=tablib --cov-report xml --cov-report term --cov-report html +addopts = -rsxX --showlocals --tb=native --cov=tablib --cov=tests --cov-report xml --cov-report term --cov-report html python_paths = . diff --git a/src/tablib/core.py b/src/tablib/core.py index 23903eb1..61c7a500 100644 --- a/src/tablib/core.py +++ b/src/tablib/core.py @@ -40,9 +40,6 @@ def __len__(self): def __repr__(self): return repr(self._row) - def __getslice__(self, i, j): - return self._row[i:j] - def __getitem__(self, i): return self._row[i] @@ -264,7 +261,7 @@ def _validate(self, row=None, col=None, safety=False): else: is_valid = (len(col) == self.height) if self.height else True else: - is_valid = all((len(x) == self.width for x in self._data)) + is_valid = all(len(x) == self.width for x in self._data) if is_valid: return True @@ -423,7 +420,7 @@ def load(self, in_stream, format=None, **kwargs): export_set, import_set = self._formats.get(format, (None, None)) if not import_set: - raise UnsupportedFormat('Format {0} cannot be imported.'.format(format)) + raise UnsupportedFormat('Format {} cannot be imported.'.format(format)) import_set(self, in_stream, **kwargs) return self @@ -436,7 +433,7 @@ def export(self, format, **kwargs): """ export_set, import_set = self._formats.get(format, (None, None)) if not export_set: - raise UnsupportedFormat('Format {0} cannot be exported.'.format(format)) + raise UnsupportedFormat('Format {} cannot be exported.'.format(format)) return export_set(self, **kwargs) @@ -1095,7 +1092,7 @@ def load(self, in_stream, format, **kwargs): export_book, import_book = self._formats.get(format, (None, None)) if not import_book: - raise UnsupportedFormat('Format {0} cannot be loaded.'.format(format)) + raise UnsupportedFormat('Format {} cannot be loaded.'.format(format)) import_book(self, in_stream, **kwargs) return self @@ -1108,7 +1105,7 @@ def export(self, format, **kwargs): """ export_book, import_book = self._formats.get(format, (None, None)) if not export_book: - raise UnsupportedFormat('Format {0} cannot be exported.'.format(format)) + raise UnsupportedFormat('Format {} cannot be exported.'.format(format)) return export_book(self, **kwargs) diff --git a/src/tablib/formats/_html.py b/src/tablib/formats/_html.py index bb311280..edf0c0a0 100644 --- a/src/tablib/formats/_html.py +++ b/src/tablib/formats/_html.py @@ -53,7 +53,7 @@ def export_book(databook): for i, dset in enumerate(databook._datasets): title = (dset.title if dset.title else 'Set %s' % (i)) - wrapper.write('<%s>%s\n' % (BOOK_ENDINGS, title, BOOK_ENDINGS)) + wrapper.write('<{}>{}\n'.format(BOOK_ENDINGS, title, BOOK_ENDINGS)) wrapper.write(dset.html) wrapper.write('\n') diff --git a/src/tablib/formats/_jira.py b/src/tablib/formats/_jira.py index 99dbf3ee..96efcf77 100644 --- a/src/tablib/formats/_jira.py +++ b/src/tablib/formats/_jira.py @@ -19,7 +19,7 @@ def export_set(dataset): header = _get_header(dataset.headers) if dataset.headers else '' body = _get_body(dataset) - return '%s\n%s' % (header, body) if header else body + return '{}\n{}'.format(header, body) if header else body def _get_body(dataset): @@ -31,6 +31,6 @@ def _get_header(headers): def _serialize_row(row, delimiter='|'): - return '%s%s%s' % (delimiter, - delimiter.join([str(item) if item else ' ' for item in row]), - delimiter) + return '{}{}{}'.format(delimiter, + delimiter.join([str(item) if item else ' ' for item in row]), + delimiter) diff --git a/src/tablib/formats/_rst.py b/src/tablib/formats/_rst.py index 8b5cfa06..8067f73e 100644 --- a/src/tablib/formats/_rst.py +++ b/src/tablib/formats/_rst.py @@ -34,7 +34,7 @@ def _max_word_len(text): 8 """ - return max((len(word) for word in text.split())) if text else 0 + return max(len(word) for word in text.split()) if text else 0 def _get_column_string_lengths(dataset): diff --git a/src/tablib/formats/_xlsx.py b/src/tablib/formats/_xlsx.py index 0a947b8b..6ac46b97 100644 --- a/src/tablib/formats/_xlsx.py +++ b/src/tablib/formats/_xlsx.py @@ -112,7 +112,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['%s%s' % (col_idx, row_number)] + cell = ws['{}{}'.format(col_idx, row_number)] # bold headers if (row_number == 1) and dataset.headers: diff --git a/src/tablib/packages/dbfpy/dbfnew.py b/src/tablib/packages/dbfpy/dbfnew.py index 29c09a1e..15620211 100644 --- a/src/tablib/packages/dbfpy/dbfnew.py +++ b/src/tablib/packages/dbfpy/dbfnew.py @@ -176,7 +176,7 @@ def write(self, filename): for i1 in range(len(dbft)): rec = dbft[i1] for fldName in dbft.fieldNames: - print('%s:\t %s' % (fldName, rec[fldName])) + print('{}:\t {}'.format(fldName, rec[fldName])) print() dbft.close() diff --git a/src/tablib/packages/dbfpy/fields.py b/src/tablib/packages/dbfpy/fields.py index c763e1ea..d6ee84ef 100644 --- a/src/tablib/packages/dbfpy/fields.py +++ b/src/tablib/packages/dbfpy/fields.py @@ -134,11 +134,7 @@ def toString(self): definition of this field. """ - if sys.version_info < (2, 4): - # earlier versions did not support padding character - _name = self.name[:11] + "\0" * (11 - len(self.name)) - else: - _name = self.name.ljust(11, '\0') + _name = self.name.ljust(11, '\0') return ( _name + self.typeCode + @@ -311,7 +307,7 @@ def decodeValue(self, value): return False if value in "YyTt": return True - raise ValueError("[%s] Invalid logical value %r" % (self.name, value)) + raise ValueError("[{}] Invalid logical value {!r}".format(self.name, value)) def encodeValue(self, value): """Return a character from the "TF?" set. diff --git a/src/tablib/packages/dbfpy/utils.py b/src/tablib/packages/dbfpy/utils.py index b3388a77..284d3eec 100644 --- a/src/tablib/packages/dbfpy/utils.py +++ b/src/tablib/packages/dbfpy/utils.py @@ -158,9 +158,6 @@ def __float__(self): def __str__(self): return "" - def __unicode__(self): - return "" - def __repr__(self): return "" diff --git a/tests/test_tablib.py b/tests/test_tablib.py index 77f2ef66..5464a8d9 100755 --- a/tests/test_tablib.py +++ b/tests/test_tablib.py @@ -4,6 +4,7 @@ import datetime import doctest import json +import pickle import unittest from uuid import uuid4 @@ -202,7 +203,7 @@ def test_data_slicing(self): self.assertEqual(self.founders[2:], [self.tom]) def test_row_slicing(self): - """Verify Row's __getslice__ method. Issue #184.""" + """Verify Row slicing. Issue #184.""" john = Row(self.john) @@ -486,6 +487,87 @@ def test_databook_formatter_with_new_lines(self): self.founders.append(('First\nSecond', 'Name', 42)) self.founders.export('xlsx') + def test_row_repr(self): + """Row repr.""" + # Arrange + john = Row(self.john) + + # Act + output = str(john) + + # Assert + self.assertEqual(output, "['John', 'Adams', 90]") + + def test_row_pickle_unpickle(self): + """Row __setstate__ and __getstate__.""" + # Arrange + before_pickle = Row(self.john) + + # Act + output = pickle.loads(pickle.dumps(before_pickle)) + + # Assert + self.assertEqual(output[0], before_pickle[0]) + self.assertEqual(output[1], before_pickle[1]) + self.assertEqual(output[2], before_pickle[2]) + + def test_row_lpush(self): + """Row lpush.""" + # Arrange + john = Row(self.john) + george = Row(self.george) + + # Act + john.lpush(george) + + # Assert + self.assertEqual(john[-1], george) + + def test_row_append(self): + """Row append.""" + # Arrange + john = Row(self.john) + george = Row(self.george) + + # Act + john.append(george) + + # Assert + self.assertEqual(john[0], george) + + def test_row_contains(self): + """Row __contains__.""" + # Arrange + john = Row(self.john) + + # Act / Assert + self.assertIn("John", john) + + def test_row_no_tag(self): + """Row has_tag.""" + # Arrange + john = Row(self.john) + + # Act / Assert + self.assertFalse(john.has_tag("not found")) + self.assertFalse(john.has_tag(None)) + + def test_row_has_tag(self): + """Row has_tag.""" + # Arrange + john = Row(self.john, tags=["tag1"]) + + # Act / Assert + self.assertTrue(john.has_tag("tag1")) + + def test_row_has_tags(self): + """Row has_tag.""" + # Arrange + john = Row(self.john, tags=["tag1", "tag2"]) + + # Act / Assert + self.assertTrue(john.has_tag(["tag2", "tag1"])) + class HTMLTests(BaseTestCase): def test_html_export(self): @@ -1012,7 +1094,7 @@ def test_dbf_import_set(self): for reg_char, data_char in zip(_dbf, data.dbf): so_far += chr(data_char) if reg_char != data_char and index not in [1, 2, 3]: - raise AssertionError('Failing at char %s: %s vs %s %s' % ( + raise AssertionError('Failing at char {}: {} vs {} {}'.format( index, reg_char, data_char, so_far)) index += 1 @@ -1055,7 +1137,7 @@ def test_dbf_export_set(self): # found_so_far += chr(data_char) if reg_char != data_char and index not in [1, 2, 3]: raise AssertionError( - 'Failing at char %s: %s vs %s (found %s)' % ( + 'Failing at char {}: {} vs {} (found {})'.format( index, reg_char, data_char, found_so_far)) index += 1 diff --git a/tests/test_tablib_dbfpy_packages_utils.py b/tests/test_tablib_dbfpy_packages_utils.py new file mode 100644 index 00000000..9288916f --- /dev/null +++ b/tests/test_tablib_dbfpy_packages_utils.py @@ -0,0 +1,170 @@ +#!/usr/bin/env python +"""Tests for tablib.packages.dbfpy.""" + +import datetime +import unittest + +from tablib.packages.dbfpy import utils + + +class UtilsUnzfillTestCase(unittest.TestCase): + """dbfpy.utils.unzfill test cases.""" + + def test_unzfill_with_nul(self): + # Arrange + text = b"abc\0xyz" + + # Act + output = utils.unzfill(text) + + # Assert + self.assertEqual(output, b"abc") + + def test_unzfill_without_nul(self): + # Arrange + text = b"abcxyz" + + # Act + output = utils.unzfill(text) + + # Assert + self.assertEqual(output, b"abcxyz") + + +class UtilsGetDateTestCase(unittest.TestCase): + """dbfpy.utils.getDate test cases.""" + + def test_getDate_none(self): + # Arrange + value = None + + # Act + output = utils.getDate(value) + + # Assert + self.assertIsInstance(output, datetime.date) + + def test_getDate_datetime_date(self): + # Arrange + value = datetime.date(2019, 10, 19) + + # Act + output = utils.getDate(value) + + # Assert + self.assertIsInstance(output, datetime.date) + self.assertEqual(output, value) + + def test_getDate_datetime_datetime(self): + # Arrange + value = datetime.datetime(2019, 10, 19, 12, 00, 00) + + # Act + output = utils.getDate(value) + + # Assert + self.assertIsInstance(output, datetime.date) + self.assertEqual(output, value) + + def test_getDate_datetime_timestamp(self): + # Arrange + value = 1571515306 + + # Act + output = utils.getDate(value) + + # Assert + self.assertIsInstance(output, datetime.date) + self.assertEqual(output, datetime.date(2019, 10, 19)) + + def test_getDate_datetime_string_yyyy_mm_dd(self): + # Arrange + value = "20191019" + + # Act + output = utils.getDate(value) + + # Assert + self.assertIsInstance(output, datetime.date) + self.assertEqual(output, datetime.date(2019, 10, 19)) + + def test_getDate_datetime_string_yymmdd(self): + # Arrange + value = "191019" + + # Act + output = utils.getDate(value) + + # Assert + self.assertIsInstance(output, datetime.date) + self.assertEqual(output, datetime.date(2019, 10, 19)) + + +class UtilsGetDateTimeTestCase(unittest.TestCase): + """dbfpy.utils.getDateTime test cases.""" + + def test_getDateTime_none(self): + # Arrange + value = None + + # Act + output = utils.getDateTime(value) + + # Assert + self.assertIsInstance(output, datetime.datetime) + + def test_getDateTime_datetime_datetime(self): + # Arrange + value = datetime.datetime(2019, 10, 19, 12, 00, 00) + + # Act + output = utils.getDateTime(value) + + # Assert + self.assertIsInstance(output, datetime.date) + self.assertEqual(output, value) + + def test_getDateTime_datetime_date(self): + # Arrange + value = datetime.date(2019, 10, 19) + + # Act + output = utils.getDateTime(value) + + # Assert + self.assertIsInstance(output, datetime.date) + self.assertEqual(output, datetime.datetime(2019, 10, 19, 00, 00)) + + def test_getDateTime_datetime_timestamp(self): + # Arrange + value = 1571515306 + + # Act + output = utils.getDateTime(value) + + # Assert + self.assertIsInstance(output, datetime.datetime) + + def test_getDateTime_datetime_string(self): + # Arrange + value = "20191019" + + # Act / Assert + with self.assertRaises(NotImplementedError): + output = utils.getDateTime(value) + + +class InvalidValueTestCase(unittest.TestCase): + """dbfpy.utils._InvalidValue test cases.""" + + def test_sanity(self): + # Arrange + INVALID_VALUE = utils.INVALID_VALUE + + # Act / Assert + self.assertEqual(INVALID_VALUE, INVALID_VALUE) + self.assertNotEqual(INVALID_VALUE, 123) + self.assertEqual(int(INVALID_VALUE), 0) + self.assertEqual(float(INVALID_VALUE), 0.0) + self.assertEqual(str(INVALID_VALUE), "") + self.assertEqual(repr(INVALID_VALUE), "")