Repository: astanin/python-tabulate
Pull request #343 (2024-09-26 16:47:47)

Apply ruff/Perflint rule PERF402
https://docs.astral.sh/ruff/rules/manual-list-copy/


Patch:
diff --git a/.circleci/config.yml b/.circleci/config.yml
new file mode 100644
index 0000000..ac9c525
--- /dev/null
+++ b/.circleci/config.yml
@@ -0,0 +1,60 @@
+# Python CircleCI 2.0 configuration file
+#
+# Check https://circleci.com/docs/2.0/language-python/ for more details
+#
+version: 2
+jobs:
+  build:
+    docker:
+      # specify the version you desire here
+      # use `-browsers` prefix for selenium tests, e.g. `3.6.1-browsers`
+      - image: circleci/python:3.8
+
+      # Specify service dependencies here if necessary
+      # CircleCI maintains a library of pre-built images
+      # documented at https://circleci.com/docs/2.0/circleci-images/
+      # - image: circleci/postgres:9.4
+
+    working_directory: ~/repo
+
+    steps:
+      - checkout
+
+      # Download and cache dependencies
+      - restore_cache:
+          keys:
+          - v1-dependencies-{{ checksum ".circleci/requirements.txt" }}
+          # fallback to using the latest cache if no exact match is found
+          - v1-dependencies-
+
+      - run:
+          name: install dependencies
+          command: |
+            python3 -m venv venv
+            . venv/bin/activate
+            pip install -r .circleci/requirements.txt
+
+      - save_cache:
+          paths:
+            - ./venv
+          key: v1-dependencies-{{ checksum ".circleci/requirements.txt" }}
+
+      - run:
+          name: build wheel
+          command: |
+            . venv/bin/activate
+            python -m build -nwx .
+
+      - store_artifacts:
+          path: dist
+          destination: dist
+
+      - run:
+          name: run tests
+          command: |
+            . venv/bin/activate
+            tox -e py38-extra
+
+      - store_artifacts:
+          path: test-reports
+          destination: test-reports
diff --git a/.circleci/requirements.txt b/.circleci/requirements.txt
new file mode 100644
index 0000000..ea5e80c
--- /dev/null
+++ b/.circleci/requirements.txt
@@ -0,0 +1,10 @@
+pytest
+tox
+numpy
+pandas
+wcwidth
+setuptools
+pip
+build
+wheel
+setuptools_scm
diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml
deleted file mode 100644
index 7e7c906..0000000
--- a/.github/workflows/lint.yml
+++ /dev/null
@@ -1,26 +0,0 @@
-name: lint
-
-on:
-  - push
-  - pull_request
-
-jobs:
-  build:
-    runs-on: ubuntu-latest
-    strategy:
-      matrix:
-        python-version: ['3.12']
-
-    steps:
-    - uses: actions/checkout@v4
-    - name: Set up Python ${{ matrix.python-version }}
-      uses: actions/setup-python@v4
-      with:
-        python-version: ${{ matrix.python-version }}
-    - name: Install dependencies
-      run: |
-        python -m pip install --upgrade pip
-        python -m pip install tox tox-gh
-    - name: Run linters
-      run: |
-        tox -e lint
diff --git a/.github/workflows/tabulate.yml b/.github/workflows/tabulate.yml
deleted file mode 100644
index 9adabb7..0000000
--- a/.github/workflows/tabulate.yml
+++ /dev/null
@@ -1,26 +0,0 @@
-name: python-tabulate
-
-on:
-  - push
-  - pull_request
-
-jobs:
-  build:
-    runs-on: ubuntu-latest
-    strategy:
-      matrix:
-        python-version: ['3.9', '3.10', '3.11', '3.12']
-
-    steps:
-    - uses: actions/checkout@v4
-    - name: Set up Python ${{ matrix.python-version }}
-      uses: actions/setup-python@v4
-      with:
-        python-version: ${{ matrix.python-version }}
-    - name: Install dependencies
-      run: |
-        python -m pip install --upgrade pip
-        python -m pip install pytest numpy pandas
-    - name: Run tests
-      run: |
-        pytest -v --doctest-modules --ignore benchmark.py
diff --git a/HOWTOPUBLISH b/HOWTOPUBLISH
index 795fc73..5be16ed 100644
--- a/HOWTOPUBLISH
+++ b/HOWTOPUBLISH
@@ -1,7 +1,7 @@
 # update contributors and CHANGELOG in README
 # tag version release
 python3 benchmark.py  # then update README
-tox -e py38-extra,py39-extra,py310-extra,py311-extra,py312-extra
+tox -e py37-extra,py38-extra,py39-extra,py310-extra
 python3 -m build -nswx .
 twine upload --repository-url https://test.pypi.org/legacy/ dist/*
 twine upload dist/*
diff --git a/README.md b/README.md
index 089f20e..07ab28c 100644
--- a/README.md
+++ b/README.md
@@ -53,7 +53,7 @@ pip install tabulate
 Build status
 ------------
 
-[![python-tabulate](https://github.com/astanin/python-tabulate/actions/workflows/tabulate.yml/badge.svg)](https://github.com/astanin/python-tabulate/actions/workflows/tabulate.yml) [![Build status](https://ci.appveyor.com/api/projects/status/8745yksvvol7h3d7/branch/master?svg=true)](https://ci.appveyor.com/project/astanin/python-tabulate/branch/master)
+[![Build status](https://circleci.com/gh/astanin/python-tabulate.svg?style=svg)](https://circleci.com/gh/astanin/python-tabulate/tree/master) [![Build status](https://ci.appveyor.com/api/projects/status/8745yksvvol7h3d7/branch/master?svg=true)](https://ci.appveyor.com/project/astanin/python-tabulate/branch/master)
 
 Library usage
 -------------
@@ -81,7 +81,7 @@ The following tabular data types are supported:
 -   list of lists or another iterable of iterables
 -   list or another iterable of dicts (keys as columns)
 -   dict of iterables (keys as columns)
--   list of dataclasses (field names as columns)
+-   list of dataclasses (Python 3.7+ only, field names as columns)
 -   two-dimensional NumPy array
 -   NumPy record arrays (names as columns)
 -   pandas.DataFrame
@@ -121,22 +121,10 @@ dictionaries or named tuples:
 ```pycon
 >>> print(tabulate({"Name": ["Alice", "Bob"],
 ...                 "Age": [24, 19]}, headers="keys"))
-Name      Age
-------  -----
-Alice      24
-Bob        19
-```
-
-When data is a list of dictionaries, a dictionary can be passed as `headers`
-to replace the keys with other column labels:
-
-```pycon
->>> print(tabulate([{1: "Alice", 2: 24}, {1: "Bob", 2: 19}],
-...                headers={1: "Name", 2: "Age"}))
-Name      Age
-------  -----
-Alice      24
-Bob        19
+  Age  Name
+-----  ------
+   24  Alice
+   19  Bob
 ```
 
 ### Row Indices
@@ -739,8 +727,13 @@ column, in which case every column may have different number formatting:
 ### Text formatting
 
 By default, `tabulate` removes leading and trailing whitespace from text
-columns. To disable whitespace removal, pass `preserve_whitespace=True`.
-Older versions of the library used a global module-level flag PRESERVE_WHITESPACE.
+columns. To disable whitespace removal, set the global module-level flag
+`PRESERVE_WHITESPACE`:
+
+```python
+import tabulate
+tabulate.PRESERVE_WHITESPACE = True
+```
 
 ### Wide (fullwidth CJK) symbols
 
@@ -1081,14 +1074,14 @@ To run tests on all supported Python versions, make sure all Python
 interpreters, `pytest` and `tox` are installed, then run `tox` in the root
 of the project source tree.
 
-On Linux `tox` expects to find executables like `python3.11`, `python3.12` etc.
-On Windows it looks for `C:\Python311\python.exe`, `C:\Python312\python.exe` etc. respectively.
+On Linux `tox` expects to find executables like `python3.7`, `python3.8` etc.
+On Windows it looks for `C:\Python37\python.exe`, `C:\Python38\python.exe` etc. respectively.
 
 One way to install all the required versions of the Python interpreter is to use [pyenv](https://github.com/pyenv/pyenv).
 All versions can then be easily installed with something like:
 
-     pyenv install 3.11.7
-     pyenv install 3.12.1
+     pyenv install 3.7.12
+     pyenv install 3.8.12
      ...
 
 Don't forget to change your `PATH` so that `tox` knows how to find all the installed versions. Something like
@@ -1096,10 +1089,10 @@ Don't forget to change your `PATH` so that `tox` knows how to find all the insta
      export PATH="${PATH}:${HOME}/.pyenv/shims"
 
 To test only some Python environments, use `-e` option. For example, to
-test only against Python 3.11 and Python 3.12, run:
+test only against Python 3.7 and Python 3.10, run:
 
 ```shell
-tox -e py311,py312
+tox -e py37,py310
 ```
 
 in the root of the project source tree.
@@ -1107,7 +1100,7 @@ in the root of the project source tree.
 To enable NumPy and Pandas tests, run:
 
 ```shell
-tox -e py311-extra,py312-extra
+tox -e py37-extra,py310-extra
 ```
 
 (this may take a long time the first time, because NumPy and Pandas will
diff --git a/appveyor.yml b/appveyor.yml
index 12bd772..4eb2dd8 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -8,14 +8,14 @@ environment:
     # The list here is complete (excluding Python 2.6, which
     # isn't covered by this document) at the time of writing.
 
-    #- PYTHON: "C:\\Python39"
-    #- PYTHON: "C:\\Python310"
-    #- PYTHON: "C:\\Python311"
-    #- PYTHON: "C:\\Python312"
+    - PYTHON: "C:\\Python37"
+    - PYTHON: "C:\\Python38"
+    - PYTHON: "C:\\Python39"
+    - PYTHON: "C:\\Python37-x64"
+    - PYTHON: "C:\\Python38-x64"
     - PYTHON: "C:\\Python39-x64"
     - PYTHON: "C:\\Python310-x64"
     - PYTHON: "C:\\Python311-x64"
-    - PYTHON: "C:\\Python312-x64"
 
 install:
   # Newer setuptools is needed for proper support of pyproject.toml
@@ -29,6 +29,9 @@ build: off
 
 test_script:
   # Put your test command here.
+  # If you don't need to build C extensions on 64-bit Python 3.3 or 3.4,
+  # you can remove "build.cmd" from the front of the command, as it's
+  # only needed to support those cases.
   # Note that you must use the environment variable %PYTHON% to refer to
   # the interpreter you're using - Appveyor does not do anything special
   # to put the Python version you want to use on PATH.
@@ -37,7 +40,9 @@ test_script:
 
 after_test:
   # This step builds your wheels.
-  # Again, you need to use %PYTHON% to get the correct interpreter
+  # Again, you only need build.cmd if you're building C extensions for
+  # 64-bit Python 3.3/3.4. And you need to use %PYTHON% to get the correct
+  # interpreter
   #- "build.cmd %PYTHON%\\python.exe setup.py bdist_wheel"
   - "%PYTHON%\\python.exe -m build -nswx ."
 
diff --git a/pyproject.toml b/pyproject.toml
index 8cb6216..5a8c1fd 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -13,13 +13,14 @@ classifiers = [
     "License :: OSI Approved :: MIT License",
     "Operating System :: OS Independent",
     "Programming Language :: Python :: 3",
+    "Programming Language :: Python :: 3.7",
+    "Programming Language :: Python :: 3.8",
     "Programming Language :: Python :: 3.9",
     "Programming Language :: Python :: 3.10",
     "Programming Language :: Python :: 3.11",
-    "Programming Language :: Python :: 3.12",
     "Topic :: Software Development :: Libraries",
 ]
-requires-python = ">=3.9"
+requires-python = ">=3.7"
 dynamic = ["version"]
 
 [project.urls]
diff --git a/tabulate/__init__.py b/tabulate/__init__.py
index 598cb4e..9e76a52 100644
--- a/tabulate/__init__.py
+++ b/tabulate/__init__.py
@@ -11,7 +11,6 @@ import re
 import math
 import textwrap
 import dataclasses
-import sys
 
 try:
     import wcwidth  # optional wide-character (CJK) support
@@ -33,6 +32,9 @@ except ImportError:
 # minimum extra space in headers
 MIN_PADDING = 2
 
+# Whether or not to preserve leading/trailing whitespace in data.
+PRESERVE_WHITESPACE = False
+
 _DEFAULT_FLOATFMT = "g"
 _DEFAULT_INTFMT = ""
 _DEFAULT_MISSINGVAL = ""
@@ -100,17 +102,12 @@ TableFormat = namedtuple(
 )
 
 
-def _is_separating_line_value(value):
-    return type(value) is str and value.strip() == SEPARATING_LINE
-
-
 def _is_separating_line(row):
     row_type = type(row)
     is_sl = (row_type == list or row_type == str) and (
-        (len(row) >= 1 and _is_separating_line_value(row[0]))
-        or (len(row) >= 2 and _is_separating_line_value(row[1]))
+        (len(row) >= 1 and row[0] == SEPARATING_LINE)
+        or (len(row) >= 2 and row[1] == SEPARATING_LINE)
     )
-
     return is_sl
 
 
@@ -137,29 +134,6 @@ def _pipe_line_with_colons(colwidths, colaligns):
     return "|" + "|".join(segments) + "|"
 
 
-def _grid_segment_with_colons(colwidth, align):
-    """Return a segment of a horizontal line with optional colons which indicate
-    column's alignment in a grid table."""
-    width = colwidth
-    if align == "right":
-        return ("=" * (width - 1)) + ":"
-    elif align == "center":
-        return ":" + ("=" * (width - 2)) + ":"
-    elif align == "left":
-        return ":" + ("=" * (width - 1))
-    else:
-        return "=" * width
-
-
-def _grid_line_with_colons(colwidths, colaligns):
-    """Return a horizontal line with optional colons to indicate column's alignment
-    in a grid table."""
-    if not colaligns:
-        colaligns = [""] * len(colwidths)
-    segments = [_grid_segment_with_colons(w, a) for a, w in zip(colaligns, colwidths)]
-    return "+" + "+".join(segments) + "+"
-
-
 def _mediawiki_row_with_attrs(separator, cell_values, colwidths, colaligns):
     alignment = {
         "left": "",
@@ -250,7 +224,7 @@ def _asciidoc_row(is_header, *args):
             colwidths, [alignment[colalign] for colalign in colaligns]
         )
         asciidoc_column_specifiers = [
-            f"{width:d}{align}" for width, align in asciidoc_alignments
+            "{:d}{}".format(width, align) for width, align in asciidoc_alignments
         ]
         header_list = ['cols="' + (",".join(asciidoc_column_specifiers)) + '"']
 
@@ -426,16 +400,6 @@ _table_formats = {
         padding=1,
         with_header_hide=None,
     ),
-    "colon_grid": TableFormat(
-        lineabove=Line("+", "-", "+", "+"),
-        linebelowheader=_grid_line_with_colons,
-        linebetweenrows=Line("+", "-", "+", "+"),
-        linebelow=Line("+", "-", "+", "+"),
-        headerrow=DataRow("|", "|", "|"),
-        datarow=DataRow("|", "|", "|"),
-        padding=1,
-        with_header_hide=None,
-    ),
     "outline": TableFormat(
         lineabove=Line("+", "-", "+", "+"),
         linebelowheader=Line("+", "=", "+", "+"),
@@ -729,7 +693,6 @@ multiline_formats = {
     "mixed_grid": "mixed_grid",
     "double_grid": "double_grid",
     "fancy_grid": "fancy_grid",
-    "colon_grid": "colon_grid",
     "pipe": "pipe",
     "orgtbl": "orgtbl",
     "jira": "jira",
@@ -1096,13 +1059,13 @@ def _choose_width_fn(has_invisible, enable_widechars, is_multiline):
     return width_fn
 
 
-def _align_column_choose_padfn(strings, alignment, has_invisible, preserve_whitespace):
+def _align_column_choose_padfn(strings, alignment, has_invisible):
     if alignment == "right":
-        if not preserve_whitespace:
+        if not PRESERVE_WHITESPACE:
             strings = [s.strip() for s in strings]
         padfn = _padleft
     elif alignment == "center":
-        if not preserve_whitespace:
+        if not PRESERVE_WHITESPACE:
             strings = [s.strip() for s in strings]
         padfn = _padboth
     elif alignment == "decimal":
@@ -1116,7 +1079,7 @@ def _align_column_choose_padfn(strings, alignment, has_invisible, preserve_white
     elif not alignment:
         padfn = _padnone
     else:
-        if not preserve_whitespace:
+        if not PRESERVE_WHITESPACE:
             strings = [s.strip() for s in strings]
         padfn = _padright
     return strings, padfn
@@ -1145,8 +1108,7 @@ def _flat_list(nested_list):
     ret = []
     for item in nested_list:
         if isinstance(item, list):
-            for subitem in item:
-                ret.append(subitem)
+            ret.extend(item)
         else:
             ret.append(item)
     return ret
@@ -1159,12 +1121,9 @@ def _align_column(
     has_invisible=True,
     enable_widechars=False,
     is_multiline=False,
-    preserve_whitespace=False,
 ):
     """[string] -> [padded_string]"""
-    strings, padfn = _align_column_choose_padfn(
-        strings, alignment, has_invisible, preserve_whitespace
-    )
+    strings, padfn = _align_column_choose_padfn(strings, alignment, has_invisible)
     width_fn = _align_column_choose_width_fn(
         has_invisible, enable_widechars, is_multiline
     )
@@ -1269,23 +1228,6 @@ def _format(val, valtype, floatfmt, intfmt, missingval="", has_invisible=True):
     if valtype is str:
         return f"{val}"
     elif valtype is int:
-        if isinstance(val, str):
-            val_striped = val.encode("unicode_escape").decode("utf-8")
-            colored = re.search(
-                r"(\\[xX]+[0-9a-fA-F]+\[\d+[mM]+)([0-9.]+)(\\.*)$", val_striped
-            )
-            if colored:
-                total_groups = len(colored.groups())
-                if total_groups == 3:
-                    digits = colored.group(2)
-                    if digits.isdigit():
-                        val_new = (
-                            colored.group(1)
-                            + format(int(digits), intfmt)
-                            + colored.group(3)
-                        )
-                        val = val_new.encode("utf-8").decode("unicode_escape")
-            intfmt = ""
         return format(val, intfmt)
     elif valtype is bytes:
         try:
@@ -1328,7 +1270,7 @@ def _align_header(
 
 
 def _remove_separating_lines(rows):
-    if isinstance(rows, list):
+    if type(rows) == list:
         separating_lines = []
         sans_rows = []
         for index, row in enumerate(rows):
@@ -1354,7 +1296,7 @@ def _prepend_row_index(rows, index):
     if isinstance(index, Sized) and len(index) != len(rows):
         raise ValueError(
             "index must be as long as the number of data rows: "
-            + f"len(index)={len(index)} len(rows)={len(rows)}"
+            + "len(index)={} len(rows)={}".format(len(index), len(rows))
         )
     sans_rows, separating_lines = _remove_separating_lines(rows)
     new_rows = []
@@ -1376,8 +1318,7 @@ def _bool(val):
 
 
 def _normalize_tabular_data(tabular_data, headers, showindex="default"):
-    """Transform a supported data type to a list of lists, and a list of headers,
-    with headers padding.
+    """Transform a supported data type to a list of lists, and a list of headers, with headers padding.
 
     Supported tabular data types:
 
@@ -1389,7 +1330,7 @@ def _normalize_tabular_data(tabular_data, headers, showindex="default"):
 
     * list of OrderedDicts (usually used with headers="keys")
 
-    * list of dataclasses (usually used with headers="keys")
+    * list of dataclasses (Python 3.7+ only, usually used with headers="keys")
 
     * 2D NumPy arrays
 
@@ -1411,27 +1352,20 @@ def _normalize_tabular_data(tabular_data, headers, showindex="default"):
 
     try:
         bool(headers)
+        is_headers2bool_broken = False  # noqa
     except ValueError:  # numpy.ndarray, pandas.core.index.Index, ...
+        is_headers2bool_broken = True  # noqa
         headers = list(headers)
 
-    err_msg = (
-        "\n\nTo build a table python-tabulate requires two-dimensional data "
-        "like a list of lists or similar."
-        "\nDid you forget a pair of extra [] or ',' in ()?"
-    )
     index = None
     if hasattr(tabular_data, "keys") and hasattr(tabular_data, "values"):
         # dict-like and pandas.DataFrame?
         if hasattr(tabular_data.values, "__call__"):
             # likely a conventional dict
             keys = tabular_data.keys()
-            try:
-                rows = list(
-                    izip_longest(*tabular_data.values())
-                )  # columns have to be transposed
-            except TypeError:  # not iterable
-                raise TypeError(err_msg)
-
+            rows = list(
+                izip_longest(*tabular_data.values())
+            )  # columns have to be transposed
         elif hasattr(tabular_data, "index"):
             # values is a property, has .index => it's likely a pandas.DataFrame (pandas 0.11.0)
             keys = list(tabular_data)
@@ -1454,10 +1388,7 @@ def _normalize_tabular_data(tabular_data, headers, showindex="default"):
             headers = list(map(str, keys))  # headers should be strings
 
     else:  # it's a usual iterable of iterables, or a NumPy array, or an iterable of dataclasses
-        try:
-            rows = list(tabular_data)
-        except TypeError:  # not iterable
-            raise TypeError(err_msg)
+        rows = list(tabular_data)
 
         if headers == "keys" and not rows:
             # an empty table (issue #81)
@@ -1525,7 +1456,7 @@ def _normalize_tabular_data(tabular_data, headers, showindex="default"):
             and len(rows) > 0
             and dataclasses.is_dataclass(rows[0])
         ):
-            # Python's dataclass
+            # Python 3.7+'s dataclass
             field_names = [field.name for field in dataclasses.fields(rows[0])]
             if headers == "keys":
                 headers = field_names
@@ -1650,7 +1581,6 @@ def tabulate(
     disable_numparse=False,
     colglobalalign=None,
     colalign=None,
-    preserve_whitespace=False,
     maxcolwidths=None,
     headersglobalalign=None,
     headersalign=None,
@@ -1669,7 +1599,7 @@ def tabulate(
     The first required argument (`tabular_data`) can be a
     list-of-lists (or another iterable of iterables), a list of named
     tuples, a dictionary of iterables, an iterable of dictionaries,
-    an iterable of dataclasses, a two-dimensional NumPy array,
+    an iterable of dataclasses (Python 3.7+), a two-dimensional NumPy array,
     NumPy record array, or a Pandas' dataframe.
 
 
@@ -1891,30 +1821,6 @@ def tabulate(
     │ eggs      │  451      │
     ╘═══════════╧═══════════╛
 
-    "colon_grid" is similar to "grid" but uses colons only to define
-    columnwise content alignment, with no whitespace padding:
-
-    >>> print(tabulate([["spam", 41.9999], ["eggs", "451.0"]],
-    ...                ["strings", "numbers"], "colon_grid"))
-    +-----------+-----------+
-    | strings   | numbers   |
-    +:==========+:==========+
-    | spam      | 41.9999   |
-    +-----------+-----------+
-    | eggs      | 451       |
-    +-----------+-----------+
-
-    >>> print(tabulate([["spam", 41.9999], ["eggs", "451.0"]],
-    ...                ["strings", "numbers"], "colon_grid",
-    ...                colalign=["right", "left"]))
-    +-----------+-----------+
-    | strings   | numbers   |
-    +==========:+:==========+
-    | spam      | 41.9999   |
-    +-----------+-----------+
-    | eggs      | 451       |
-    +-----------+-----------+
-
     "outline" is the same as the "grid" format but doesn't draw lines between rows:
 
     >>> print(tabulate([["spam", 41.9999], ["eggs", "451.0"]],
@@ -2231,13 +2137,6 @@ def tabulate(
         numalign = "decimal" if numalign == _DEFAULT_ALIGN else numalign
         stralign = "left" if stralign == _DEFAULT_ALIGN else stralign
 
-    # 'colon_grid' uses colons in the line beneath the header to represent a column's
-    # alignment instead of literally aligning the text differently. Hence,
-    # left alignment of the data in the text output is enforced.
-    if tablefmt == "colon_grid":
-        colglobalalign = "left"
-        headersglobalalign = "left"
-
     # optimization: look for ANSI control codes once,
     # enable smart width functions only if a control code is found
     #
@@ -2302,19 +2201,15 @@ def tabulate(
 
     # align columns
     # first set global alignment
-    if colglobalalign is not None:  # if global alignment provided
+    if colglobalalign is not None: # if global alignment provided
         aligns = [colglobalalign] * len(cols)
-    else:  # default
+    else: # default
         aligns = [numalign if ct in [int, float] else stralign for ct in coltypes]
-    # then specific alignments
+    # then specific alignements
     if colalign is not None:
         assert isinstance(colalign, Iterable)
         if isinstance(colalign, str):
-            warnings.warn(
-                f"As a string, `colalign` is interpreted as {[c for c in colalign]}. "
-                f'Did you mean `colglobalalign = "{colalign}"` or `colalign = ("{colalign}",)`?',
-                stacklevel=2,
-            )
+            warnings.warn(f"As a string, `colalign` is interpreted as {[c for c in colalign]}. Did you mean `colglobalalign = \"{colalign}\"` or `colalign = (\"{colalign}\",)`?", stacklevel=2)
         for idx, align in enumerate(colalign):
             if not idx < len(aligns):
                 break
@@ -2323,22 +2218,9 @@ def tabulate(
     minwidths = (
         [width_fn(h) + min_padding for h in headers] if headers else [0] * len(cols)
     )
-    aligns_copy = aligns.copy()
-    # Reset alignments in copy of alignments list to "left" for 'colon_grid' format,
-    # which enforces left alignment in the text output of the data.
-    if tablefmt == "colon_grid":
-        aligns_copy = ["left"] * len(cols)
     cols = [
-        _align_column(
-            c,
-            a,
-            minw,
-            has_invisible,
-            enable_widechars,
-            is_multiline,
-            preserve_whitespace,
-        )
-        for c, a, minw in zip(cols, aligns_copy, minwidths)
+        _align_column(c, a, minw, has_invisible, enable_widechars, is_multiline)
+        for c, a, minw in zip(cols, aligns, minwidths)
     ]
 
     aligns_headers = None
@@ -2346,25 +2228,20 @@ def tabulate(
         # align headers and add headers
         t_cols = cols or [[""]] * len(headers)
         # first set global alignment
-        if headersglobalalign is not None:  # if global alignment provided
+        if headersglobalalign is not None: # if global alignment provided
             aligns_headers = [headersglobalalign] * len(t_cols)
-        else:  # default
+        else: # default
             aligns_headers = aligns or [stralign] * len(headers)
-        # then specific header alignments
+        # then specific header alignements
         if headersalign is not None:
             assert isinstance(headersalign, Iterable)
             if isinstance(headersalign, str):
-                warnings.warn(
-                    f"As a string, `headersalign` is interpreted as {[c for c in headersalign]}. "
-                    f'Did you mean `headersglobalalign = "{headersalign}"` '
-                    f'or `headersalign = ("{headersalign}",)`?',
-                    stacklevel=2,
-                )
+                warnings.warn(f"As a string, `headersalign` is interpreted as {[c for c in headersalign]}. Did you mean `headersglobalalign = \"{headersalign}\"` or `headersalign = (\"{headersalign}\",)`?", stacklevel=2)
             for idx, align in enumerate(headersalign):
                 hidx = headers_pad + idx
                 if not hidx < len(aligns_headers):
                     break
-                elif align == "same" and hidx < len(aligns):  # same as column align
+                elif align == "same" and hidx < len(aligns): # same as column align
                     aligns_headers[hidx] = aligns[hidx]
                 elif align != "global":
                     aligns_headers[hidx] = align
@@ -2389,14 +2266,7 @@ def tabulate(
     _reinsert_separating_lines(rows, separating_lines)
 
     return _format_table(
-        tablefmt,
-        headers,
-        aligns_headers,
-        rows,
-        minwidths,
-        aligns,
-        is_multiline,
-        rowaligns=rowaligns,
+        tablefmt, headers, aligns_headers, rows, minwidths, aligns, is_multiline, rowaligns=rowaligns
     )
 
 
@@ -2433,8 +2303,6 @@ def _expand_iterable(original, num_desired, default):
 
 def _pad_row(cells, padding):
     if cells:
-        if cells == SEPARATING_LINE:
-            return SEPARATING_LINE
         pad = " " * padding
         padded_cells = [pad + cell + pad for cell in cells]
         return padded_cells
@@ -2529,9 +2397,7 @@ class JupyterHTMLStr(str):
         return self
 
 
-def _format_table(
-    fmt, headers, headersaligns, rows, colwidths, colaligns, is_multiline, rowaligns
-):
+def _format_table(fmt, headers, headersaligns, rows, colwidths, colaligns, is_multiline, rowaligns):
     """Produce a plain-text representation of the table."""
     lines = []
     hidden = fmt.with_header_hide if (headers and fmt.with_header_hide) else []
@@ -2560,10 +2426,9 @@ def _format_table(
     if padded_rows and fmt.linebetweenrows and "linebetweenrows" not in hidden:
         # initial rows with a line below
         for row, ralign in zip(padded_rows[:-1], rowaligns):
-            if row != SEPARATING_LINE:
-                append_row(
-                    lines, row, padded_widths, colaligns, fmt.datarow, rowalign=ralign
-                )
+            append_row(
+                lines, row, padded_widths, colaligns, fmt.datarow, rowalign=ralign
+            )
             _append_line(lines, padded_widths, colaligns, fmt.linebetweenrows)
         # the last row without a line below
         append_row(
@@ -2828,22 +2693,15 @@ def _main():
                               (default: simple)
     """
     import getopt
+    import sys
+    import textwrap
 
     usage = textwrap.dedent(_main.__doc__)
     try:
         opts, args = getopt.getopt(
             sys.argv[1:],
-            "h1o:s:F:I:f:",
-            [
-                "help",
-                "header",
-                "output=",
-                "sep=",
-                "float=",
-                "int=",
-                "colalign=",
-                "format=",
-            ],
+            "h1o:s:F:A:f:",
+            ["help", "header", "output", "sep=", "float=", "int=", "align=", "format="],
         )
     except getopt.GetoptError as e:
         print(e)
@@ -2879,7 +2737,7 @@ def _main():
             print(usage)
             sys.exit(0)
     files = [sys.stdin] if not args else args
-    with sys.stdout if outfile == "-" else open(outfile, "w") as out:
+    with (sys.stdout if outfile == "-" else open(outfile, "w")) as out:
         for f in files:
             if f == "-":
                 f = sys.stdin
diff --git a/tox.ini b/tox.ini
index a7fe513..c6260d2 100644
--- a/tox.ini
+++ b/tox.ini
@@ -8,16 +8,9 @@
 # for testing and it is disabled by default.
 
 [tox]
-envlist = lint, py{38, 39, 310, 311, 312}
+envlist = lint, py{37, 38, 39, 310, 311}
 isolated_build = True
 
-[gh]
-python =
-    3.9: py39-extra
-    3.10: py310-extra
-    3.11: py311-extra
-    3.12: py312-extra
-
 [testenv]
 commands = pytest -v --doctest-modules --ignore benchmark.py {posargs}
 deps =
@@ -32,6 +25,21 @@ commands = python -m pre_commit run -a
 deps =
     pre-commit
 
+[testenv:py37]
+basepython = python3.7
+commands = pytest -v --doctest-modules --ignore benchmark.py {posargs}
+deps =
+    pytest
+
+[testenv:py37-extra]
+basepython = python3.7
+commands = pytest -v --doctest-modules --ignore benchmark.py {posargs}
+deps =
+    pytest
+    numpy
+    pandas
+    wcwidth
+
 [testenv:py38]
 basepython = python3.8
 commands = pytest -v --doctest-modules --ignore benchmark.py {posargs}
@@ -97,21 +105,6 @@ deps =
     pandas
     wcwidth
 
-[testenv:py312]
-basepython = python3.12
-commands = pytest -v --doctest-modules --ignore benchmark.py {posargs}
-deps =
-    pytest
-
-[testenv:py312-extra]
-basepython = python3.12
-setenv = PYTHONDEVMODE = 1
-commands = pytest -v --doctest-modules --ignore benchmark.py {posargs}
-deps =
-    pytest
-    numpy
-    pandas
-    wcwidth
 
 [flake8]
 max-complexity = 22


Test patch:
diff --git a/test/common.py b/test/common.py
index fe33259..4cd3709 100644
--- a/test/common.py
+++ b/test/common.py
@@ -2,7 +2,6 @@ import pytest  # noqa
 from pytest import skip, raises  # noqa
 import warnings
 
-
 def assert_equal(expected, result):
     print("Expected:\n%s\n" % expected)
     print("Got:\n%s\n" % result)
@@ -29,7 +28,6 @@ def rows_to_pipe_table_str(rows):
 
     return "\n".join(lines)
 
-
 def check_warnings(func_args_kwargs, *, num=None, category=None, contain=None):
     func, args, kwargs = func_args_kwargs
     with warnings.catch_warnings(record=True) as W:
@@ -43,3 +41,4 @@ def check_warnings(func_args_kwargs, *, num=None, category=None, contain=None):
             assert all([issubclass(w.category, category) for w in W])
         if contain is not None:
             assert all([contain in str(w.message) for w in W])
+
diff --git a/test/test_api.py b/test/test_api.py
index 062573c..e658e82 100644
--- a/test/test_api.py
+++ b/test/test_api.py
@@ -50,7 +50,6 @@ def test_tabulate_signature():
         ("disable_numparse", False),
         ("colglobalalign", None),
         ("colalign", None),
-        ("preserve_whitespace", False),
         ("maxcolwidths", None),
         ("headersglobalalign", None),
         ("headersalign", None),
diff --git a/test/test_cli.py b/test/test_cli.py
index e71572d..ce85f19 100644
--- a/test/test_cli.py
+++ b/test/test_cli.py
@@ -2,6 +2,7 @@
 
 """
 
+
 import os
 import sys
 
diff --git a/test/test_input.py b/test/test_input.py
index 721d03a..a178bd9 100644
--- a/test/test_input.py
+++ b/test/test_input.py
@@ -522,7 +522,7 @@ def test_py37orlater_list_of_dataclasses_headers():
 
 def test_list_bytes():
     "Input: a list of bytes. (issue #192)"
-    lb = [["你好".encode()], ["你好"]]
+    lb = [["你好".encode("utf-8")], ["你好"]]
     expected = "\n".join(
         ["bytes", "---------------------------", r"b'\xe4\xbd\xa0\xe5\xa5\xbd'", "你好"]
     )
diff --git a/test/test_internal.py b/test/test_internal.py
index e7564d3..64e1d12 100644
--- a/test/test_internal.py
+++ b/test/test_internal.py
@@ -182,12 +182,7 @@ def test_wrap_text_wide_chars():
 
     rows = [["청자청자청자청자청자", "약간 감싸면 더 잘 보일 수있는 다소 긴 설명입니다"]]
     widths = [5, 20]
-    expected = [
-        [
-            "청자\n청자\n청자\n청자\n청자",
-            "약간 감싸면 더 잘\n보일 수있는 다소 긴\n설명입니다",
-        ]
-    ]
+    expected = [["청자\n청자\n청자\n청자\n청자", "약간 감싸면 더 잘\n보일 수있는 다소 긴\n설명입니다"]]
     result = T._wrap_text_to_colwidths(rows, widths)
 
     assert_equal(expected, result)
diff --git a/test/test_output.py b/test/test_output.py
index 059742c..d572498 100644
--- a/test/test_output.py
+++ b/test/test_output.py
@@ -1,6 +1,6 @@
 """Test output of the various forms of tabular data."""
-from pytest import mark
 
+import tabulate as tabulate_module
 from common import assert_equal, raises, skip, check_warnings
 from tabulate import tabulate, simple_separated_format, SEPARATING_LINE
 
@@ -136,10 +136,7 @@ def test_plain_maxcolwidth_autowraps_wide_chars():
 
     table = [
         ["hdr", "fold"],
-        [
-            "1",
-            "약간 감싸면 더 잘 보일 수있는 다소 긴 설명입니다 설명입니다 설명입니다 설명입니다 설명",
-        ],
+        ["1", "약간 감싸면 더 잘 보일 수있는 다소 긴 설명입니다 설명입니다 설명입니다 설명입니다 설명"],
     ]
     expected = "\n".join(
         [
@@ -338,36 +335,6 @@ def test_simple_headerless_with_sep_line():
     assert_equal(expected, result)
 
 
-def test_simple_headerless_with_sep_line_with_padding_in_tablefmt():
-    "Output: simple without headers with sep line with padding in tablefmt"
-    expected = "\n".join(
-        [
-            "|------|----------|",
-            "| spam |  41.9999 |",
-            "|------|----------|",
-            "| eggs | 451      |",
-        ]
-    )
-    result = tabulate(_test_table_with_sep_line, tablefmt="github")
-    assert_equal(expected, result)
-
-
-def test_simple_headerless_with_sep_line_with_linebetweenrows_in_tablefmt():
-    "Output: simple without headers with sep line with linebetweenrows in tablefmt"
-    expected = "\n".join(
-        [
-            "+------+----------+",
-            "| spam |  41.9999 |",
-            "+------+----------+",
-            "+------+----------+",
-            "| eggs | 451      |",
-            "+------+----------+",
-        ]
-    )
-    result = tabulate(_test_table_with_sep_line, tablefmt="grid")
-    assert_equal(expected, result)
-
-
 def test_simple_multiline_headerless():
     "Output: simple with multiline cells without headers"
     table = [["foo bar\nbaz\nbau", "hello"], ["", "multiline\nworld"]]
@@ -1447,104 +1414,6 @@ def test_fancy_grid_multiline_row_align():
     assert_equal(expected, result)
 
 
-def test_colon_grid():
-    "Output: colon_grid with two columns aligned left and center"
-    expected = "\n".join(
-        [
-            "+------+------+",
-            "| H1   | H2   |",
-            "+=====:+:====:+",
-            "| 3    | 4    |",
-            "+------+------+",
-        ]
-    )
-    result = tabulate(
-        [[3, 4]],
-        headers=("H1", "H2"),
-        tablefmt="colon_grid",
-        colalign=["right", "center"],
-    )
-    assert_equal(expected, result)
-
-
-def test_colon_grid_wide_characters():
-    "Output: colon_grid with wide chars in header"
-    try:
-        import wcwidth  # noqa
-    except ImportError:
-        skip("test_colon_grid_wide_characters is skipped")
-    headers = list(_test_table_headers)
-    headers[1] = "配列"
-    expected = "\n".join(
-        [
-            "+-----------+---------+",
-            "| strings   | 配列    |",
-            "+:==========+========:+",
-            "| spam      | 41.9999 |",
-            "+-----------+---------+",
-            "| eggs      | 451     |",
-            "+-----------+---------+",
-        ]
-    )
-    result = tabulate(
-        _test_table, headers, tablefmt="colon_grid", colalign=["left", "right"]
-    )
-    assert_equal(expected, result)
-
-
-def test_colon_grid_headerless():
-    "Output: colon_grid without headers"
-    expected = "\n".join(
-        [
-            "+------+---------+",
-            "| spam | 41.9999 |",
-            "+------+---------+",
-            "| eggs | 451     |",
-            "+------+---------+",
-        ]
-    )
-    result = tabulate(_test_table, tablefmt="colon_grid")
-    assert_equal(expected, result)
-
-
-def test_colon_grid_multiline():
-    "Output: colon_grid with multiline cells"
-    table = [["Data\n5", "33\n3"]]
-    headers = ["H1\n1", "H2\n2"]
-    expected = "\n".join(
-        [
-            "+------+------+",
-            "| H1   | H2   |",
-            "| 1    | 2    |",
-            "+:=====+:=====+",
-            "| Data | 33   |",
-            "| 5    | 3    |",
-            "+------+------+",
-        ]
-    )
-    result = tabulate(table, headers, tablefmt="colon_grid")
-    assert_equal(expected, result)
-
-
-def test_colon_grid_with_empty_cells():
-    table = [["A", ""], ["", "B"]]
-    headers = ["H1", "H2"]
-    alignments = ["center", "right"]
-    expected = "\n".join(
-        [
-            "+------+------+",
-            "| H1   | H2   |",
-            "+:====:+=====:+",
-            "| A    |      |",
-            "+------+------+",
-            "|      | B    |",
-            "+------+------+",
-        ]
-    )
-    result = tabulate(table, headers, tablefmt="colon_grid", colalign=alignments)
-    assert_equal(expected, result)
-
-
 def test_outline():
     "Output: outline with headers"
     expected = "\n".join(
@@ -2769,46 +2638,6 @@ def test_intfmt():
     assert_equal(expected, result)
 
 
-def test_intfmt_with_string_as_integer():
-    "Output: integer format"
-    result = tabulate([[82642], ["1500"], [2463]], intfmt=",", tablefmt="plain")
-    expected = "82,642\n  1500\n 2,463"
-    assert_equal(expected, result)
-
-
-@mark.skip(reason="It detects all values as floats but there are strings and integers.")
-def test_intfmt_with_string_with_floats():
-    "Output: integer format"
-    result = tabulate(
-        [[82000.38], ["1500.47"], ["2463"], [92165]], intfmt=",", tablefmt="plain"
-    )
-    expected = "82000.4\n 1500.47\n 2463\n92,165"
-    assert_equal(expected, result)
-
-
-def test_intfmt_with_colors():
-    "Regression: Align ANSI-colored values as if they were colorless."
-    colortable = [
-        ("\x1b[33mabc\x1b[0m", 42, "\x1b[31m42\x1b[0m"),
-        ("\x1b[35mdef\x1b[0m", 987654321, "\x1b[32m987654321\x1b[0m"),
-    ]
-    colorheaders = ("test", "\x1b[34mtest\x1b[0m", "test")
-    formatted = tabulate(colortable, colorheaders, "grid", intfmt=",")
-    expected = "\n".join(
-        [
-            "+--------+-------------+-------------+",
-            "| test   |        \x1b[34mtest\x1b[0m |        test |",
-            "+========+=============+=============+",
-            "| \x1b[33mabc\x1b[0m    |          42 |          \x1b[31m42\x1b[0m |",
-            "+--------+-------------+-------------+",
-            "| \x1b[35mdef\x1b[0m    | 987,654,321 | \x1b[32m987,654,321\x1b[0m |",
-            "+--------+-------------+-------------+",
-        ]
-    )
-    print(f"expected: {expected!r}\n\ngot:      {formatted!r}\n")
-    assert_equal(expected, formatted)
-
-
 def test_empty_data_with_headers():
     "Output: table with empty data and headers as firstrow"
     expected = ""
@@ -2851,72 +2680,60 @@ def test_colalign_multi_with_sep_line():
     expected = "  one  two\n\nthree  four"
     assert_equal(expected, result)
 
-
 def test_column_global_and_specific_alignment():
-    """Test `colglobalalign` and `"global"` parameter for `colalign`."""
-    table = [[1, 2, 3, 4], [111, 222, 333, 444]]
-    colglobalalign = "center"
-    colalign = ("global", "left", "right")
+    """ Test `colglobalalign` and `"global"` parameter for `colalign`. """
+    table = [[1,2,3,4],[111,222,333,444]]
+    colglobalalign = 'center'
+    colalign = ('global','left', 'right')
     result = tabulate(table, colglobalalign=colglobalalign, colalign=colalign)
-    expected = "\n".join(
-        [
-            "---  ---  ---  ---",
-            " 1   2      3   4",
-            "111  222  333  444",
-            "---  ---  ---  ---",
-        ]
-    )
+    expected = '\n'.join([
+        "---  ---  ---  ---",
+        " 1   2      3   4",
+        "111  222  333  444",
+        "---  ---  ---  ---"])
     assert_equal(expected, result)
 
-
 def test_headers_global_and_specific_alignment():
-    """Test `headersglobalalign` and `headersalign`."""
-    table = [[1, 2, 3, 4, 5, 6], [111, 222, 333, 444, 555, 666]]
-    colglobalalign = "center"
-    colalign = ("left",)
-    headers = ["h", "e", "a", "d", "e", "r"]
-    headersglobalalign = "right"
-    headersalign = ("same", "same", "left", "global", "center")
-    result = tabulate(
-        table,
-        headers=headers,
-        colglobalalign=colglobalalign,
-        colalign=colalign,
-        headersglobalalign=headersglobalalign,
-        headersalign=headersalign,
-    )
-    expected = "\n".join(
-        [
-            "h     e   a      d   e     r",
-            "---  ---  ---  ---  ---  ---",
-            "1     2    3    4    5    6",
-            "111  222  333  444  555  666",
-        ]
-    )
+    """ Test `headersglobalalign` and `headersalign`. """
+    table = [[1,2,3,4,5,6],[111,222,333,444,555,666]]
+    colglobalalign = 'center'
+    colalign = ('left',)
+    headers = ['h', 'e', 'a', 'd', 'e', 'r']
+    headersglobalalign = 'right'
+    headersalign = ('same', 'same', 'left', 'global', 'center')
+    result = tabulate(table, headers=headers, colglobalalign=colglobalalign, colalign=colalign, headersglobalalign=headersglobalalign, headersalign=headersalign)
+    expected = '\n'.join([
+        "h     e   a      d   e     r",
+        "---  ---  ---  ---  ---  ---",
+        "1     2    3    4    5    6",
+        "111  222  333  444  555  666"])
     assert_equal(expected, result)
 
-
 def test_colalign_or_headersalign_too_long():
-    """Test `colalign` and `headersalign` too long."""
-    table = [[1, 2], [111, 222]]
-    colalign = ("global", "left", "center")
-    headers = ["h"]
-    headersalign = ("center", "right", "same")
-    result = tabulate(
-        table, headers=headers, colalign=colalign, headersalign=headersalign
-    )
-    expected = "\n".join(["      h", "---  ---", "  1  2", "111  222"])
+    """ Test `colalign` and `headersalign` too long. """
+    table = [[1,2],[111,222]]
+    colalign = ('global', 'left', 'center')
+    headers = ['h']
+    headersalign = ('center', 'right', 'same')
+    result = tabulate(table, headers=headers, colalign=colalign, headersalign=headersalign)
+    expected = '\n'.join([
+        "      h",
+        "---  ---",
+        "  1  2",
+        "111  222"])
     assert_equal(expected, result)
 
-
 def test_warning_when_colalign_or_headersalign_is_string():
-    """Test user warnings when `colalign` or `headersalign` is a string."""
-    table = [[1, "bar"]]
-    opt = {"colalign": "center", "headers": ["foo", "2"], "headersalign": "center"}
-    check_warnings(
-        (tabulate, [table], opt), num=2, category=UserWarning, contain="As a string"
-    )
-
+    """ Test user warnings when `colalign` or `headersalign` is a string. """
+    table = [[1,"bar"]]
+    opt = {
+        'colalign': "center",
+        'headers': ['foo', '2'],
+        'headersalign': "center"}
+    check_warnings((tabulate, [table], opt),
+        num = 2,
+        category = UserWarning,
+        contain = "As a string")
 
 def test_float_conversions():
     "Output: float format parsed"
@@ -3118,27 +2935,6 @@ def test_list_of_lists_with_index_with_sep_line():
     assert_equal(expected, result)
 
 
-def test_with_padded_columns_with_sep_line():
-    table = [
-        ["1", "one"],  # "1" as a str on purpose
-        [1_000, "one K"],
-        SEPARATING_LINE,
-        [1_000_000, "one M"],
-    ]
-    expected = "\n".join(
-        [
-            "+---------+-------+",
-            "|       1 | one   |",
-            "|    1000 | one K |",
-            "|---------+-------|",
-            "| 1000000 | one M |",
-            "+---------+-------+",
-        ]
-    )
-    result = tabulate(table, tablefmt="psql")
-    assert_equal(expected, result)
-
-
 def test_list_of_lists_with_supplied_index():
     "Output: a table with a supplied index"
     dd = zip(*[list(range(3)), list(range(101, 104))])
@@ -3216,16 +3012,18 @@ def test_disable_numparse_list():
 
 def test_preserve_whitespace():
     "Output: Default table output, but with preserved leading whitespace."
+    tabulate_module.PRESERVE_WHITESPACE = True
     table_headers = ["h1", "h2", "h3"]
     test_table = [["  foo", " bar   ", "foo"]]
     expected = "\n".join(
         ["h1     h2       h3", "-----  -------  ----", "  foo   bar     foo"]
     )
-    result = tabulate(test_table, table_headers, preserve_whitespace=True)
+    result = tabulate(test_table, table_headers)
     assert_equal(expected, result)
 
+    tabulate_module.PRESERVE_WHITESPACE = False
     table_headers = ["h1", "h2", "h3"]
     test_table = [["  foo", " bar   ", "foo"]]
     expected = "\n".join(["h1    h2    h3", "----  ----  ----", "foo   bar   foo"])
-    result = tabulate(test_table, table_headers, preserve_whitespace=False)
+    result = tabulate(test_table, table_headers)
     assert_equal(expected, result)
diff --git a/test/test_regression.py b/test/test_regression.py
index 5289db0..8f60ce7 100644
--- a/test/test_regression.py
+++ b/test/test_regression.py
@@ -512,15 +512,3 @@ def test_numpy_int64_as_integer():
         assert_equal(expected, result)
     except ImportError:
         raise skip("")
-
-
-def test_empty_table_with_colalign():
-    "Regression: empty table with colalign kwarg"
-    table = tabulate([], ["a", "b", "c"], colalign=("center", "left", "left", "center"))
-    expected = "\n".join(
-        [
-            "a    b    c",
-            "---  ---  ---",
-        ]
-    )
-    assert_equal(expected, table)
diff --git a/test/test_textwrapper.py b/test/test_textwrapper.py
index b315a5c..f3070b1 100644
--- a/test/test_textwrapper.py
+++ b/test/test_textwrapper.py
@@ -1,4 +1,6 @@
 """Discretely test functionality of our custom TextWrapper"""
+from __future__ import unicode_literals
+
 import datetime
 
 from tabulate import _CustomTextWrap as CTW, tabulate
@@ -142,8 +144,7 @@ def test_wrap_color_in_single_line():
 
 def test_wrap_color_line_splillover():
     """TextWrapper: Wrap a line - preserve internal color tags and wrap them to
-    other lines when required, requires adding the colors tags to other lines as appropriate
-    """
+    other lines when required, requires adding the colors tags to other lines as appropriate"""
     # This has both a text color and a background color
     data = "This is a \033[31mtest string for testing TextWrap\033[0m with colors"