From 8c5404591b6f7d9f253c9c38f4642918fc832400 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Fri, 30 Oct 2020 19:01:48 +0200 Subject: [PATCH] Add support for Python 3.9, drop EOL 3.5 (#477) --- .github/workflows/docs-lint.yml | 26 +++++++++++++++++++++----- .github/workflows/test.yml | 24 ++++++++++++++++++++---- .pre-commit-config.yaml | 12 ++++++------ .travis.yml | 1 + HISTORY.md | 8 ++++++++ docs/intro.rst | 2 +- pyproject.toml | 7 +------ setup.py | 4 ++-- src/tablib/core.py | 10 +++++----- src/tablib/formats/__init__.py | 2 +- src/tablib/formats/_html.py | 2 +- src/tablib/formats/_jira.py | 2 +- src/tablib/formats/_xls.py | 3 ++- src/tablib/formats/_xlsx.py | 5 +++-- src/tablib/formats/_yaml.py | 3 ++- src/tablib/packages/dbfpy/fields.py | 2 +- tests/test_tablib.py | 3 ++- tox.ini | 5 +++-- 18 files changed, 81 insertions(+), 40 deletions(-) diff --git a/.github/workflows/docs-lint.yml b/.github/workflows/docs-lint.yml index c0e240a..0333ec7 100644 --- a/.github/workflows/docs-lint.yml +++ b/.github/workflows/docs-lint.yml @@ -2,23 +2,39 @@ name: Docs and lint on: [push, pull_request] +env: + FORCE_COLOR: 1 + jobs: build: runs-on: ubuntu-latest strategy: matrix: - python-version: [3.8] env: - TOXENV: docs - TOXENV: lint steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v2 - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v1 + - name: Set up Python + uses: actions/setup-python@v2 with: - python-version: ${{ matrix.python-version }} + python-version: 3.9 + + - name: Get pip cache dir + id: pip-cache + run: | + echo "::set-output name=dir::$(pip cache dir)" + + - name: Cache + uses: actions/cache@v2 + with: + path: ${{ steps.pip-cache.outputs.dir }} + key: + ${{ matrix.os }}-${{ matrix.python-version }}-v1-${{ hashFiles('**/setup.py') }} + restore-keys: | + ${{ matrix.os }}-${{ matrix.python-version }}-v1- - name: Install dependencies run: | diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index aac8fdb..3b4c630 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -2,24 +2,40 @@ name: Test on: [push, pull_request] +env: + FORCE_COLOR: 1 + jobs: build: - runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: - python-version: [3.5, 3.6, 3.7, 3.8] + python-version: [3.6, 3.7, 3.8, 3.9] os: [ubuntu-latest, macOS-latest, windows-latest] steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v2 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v1 + uses: actions/setup-python@v2 with: python-version: ${{ matrix.python-version }} + - name: Get pip cache dir + id: pip-cache + run: | + echo "::set-output name=dir::$(pip cache dir)" + + - name: Cache + uses: actions/cache@v2 + with: + path: ${{ steps.pip-cache.outputs.dir }} + key: + ${{ matrix.os }}-${{ matrix.python-version }}-v1-${{ hashFiles('**/setup.py') }} + restore-keys: | + ${{ matrix.os }}-${{ matrix.python-version }}-v1- + - name: Install dependencies run: | python -m pip install --upgrade pip diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 20f3363..88ff23c 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,24 +1,24 @@ repos: - repo: https://github.com/asottile/pyupgrade - rev: v1.26.2 + rev: v2.7.3 hooks: - id: pyupgrade - args: ["--py3-plus"] + args: ["--py36-plus"] - - repo: https://github.com/pre-commit/mirrors-isort - rev: v4.3.21 + - repo: https://github.com/PyCQA/isort + rev: 5.6.4 hooks: - id: isort additional_dependencies: [toml] - repo: https://github.com/pre-commit/pygrep-hooks - rev: v1.4.4 + rev: v1.7.0 hooks: - id: python-check-blanket-noqa - id: rst-backticks - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v2.4.0 + rev: v3.3.0 hooks: - id: check-merge-conflict - id: check-toml diff --git a/.travis.yml b/.travis.yml index 51d9452..4861aed 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,6 +11,7 @@ matrix: env: TOXENV=docs - python: 3.8 env: TOXENV=lint + - python: 3.9 - python: 3.8 - python: 3.7 - python: 3.6 diff --git a/HISTORY.md b/HISTORY.md index 1a90bb0..00b849b 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -2,6 +2,14 @@ ## Unreleased +### Breaking changes + +- Dropped Python 3.5 support + +### Improvements + +- Added Python 3.9 support + ### Bugfixes - Prevented crash in rst export with only-space strings (#469). diff --git a/docs/intro.rst b/docs/intro.rst index 8362bf8..62b26e6 100644 --- a/docs/intro.rst +++ b/docs/intro.rst @@ -57,7 +57,7 @@ THE SOFTWARE. Pythons Supported ----------------- -Python 3.5+ is officially supported. +Python 3.6+ is officially supported. Now, go :ref:`install Tablib `. diff --git a/pyproject.toml b/pyproject.toml index f19f1d5..5d7bf33 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,2 @@ [tool.isort] -force_grid_wrap = 0 -include_trailing_comma = true -known_third_party = ["MarkupPy", "odf", "openpyxl", "setuptools", "tablib", "xlrd", "xlwt", "yaml"] -line_length = 88 -multi_line_output = 3 -use_parentheses = true +profile = "black" diff --git a/setup.py b/setup.py index 0f3da66..0687d1d 100755 --- a/setup.py +++ b/setup.py @@ -33,12 +33,12 @@ setup( 'Programming Language :: Python', 'Programming Language :: Python :: 3 :: Only', 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.8', + 'Programming Language :: Python :: 3.9', ], - python_requires='>=3.5', + python_requires='>=3.6', extras_require={ 'all': ['markuppy', 'odfpy', 'openpyxl>=2.6.0', 'pandas', 'pyyaml', 'tabulate', 'xlrd', 'xlwt'], 'cli': ['tabulate'], diff --git a/src/tablib/core.py b/src/tablib/core.py index 4fbe76a..468cf28 100644 --- a/src/tablib/core.py +++ b/src/tablib/core.py @@ -414,10 +414,10 @@ class Dataset: fmt = registry.get_format(format) if not hasattr(fmt, 'import_set'): - raise UnsupportedFormat('Format {} cannot be imported.'.format(format)) + raise UnsupportedFormat(f'Format {format} cannot be imported.') if not import_set: - raise UnsupportedFormat('Format {} cannot be imported.'.format(format)) + raise UnsupportedFormat(f'Format {format} cannot be imported.') fmt.import_set(self, stream, **kwargs) return self @@ -430,7 +430,7 @@ class Dataset: """ fmt = registry.get_format(format) if not hasattr(fmt, 'export_set'): - raise UnsupportedFormat('Format {} cannot be exported.'.format(format)) + raise UnsupportedFormat(f'Format {format} cannot be exported.') return fmt.export_set(self, **kwargs) @@ -875,7 +875,7 @@ class Databook: fmt = registry.get_format(format) if not hasattr(fmt, 'import_book'): - raise UnsupportedFormat('Format {} cannot be loaded.'.format(format)) + raise UnsupportedFormat(f'Format {format} cannot be loaded.') fmt.import_book(self, stream, **kwargs) return self @@ -888,7 +888,7 @@ class Databook: """ fmt = registry.get_format(format) if not hasattr(fmt, 'export_book'): - raise UnsupportedFormat('Format {} cannot be exported.'.format(format)) + raise UnsupportedFormat(f'Format {format} cannot be exported.') return fmt.export_book(self, **kwargs) diff --git a/src/tablib/formats/__init__.py b/src/tablib/formats/__init__.py index 31b54db..0756ab1 100644 --- a/src/tablib/formats/__init__.py +++ b/src/tablib/formats/__init__.py @@ -28,7 +28,7 @@ def load_format_class(dotted_path): module_path, class_name = dotted_path.rsplit('.', 1) return getattr(import_module(module_path), class_name) except (ValueError, AttributeError) as err: - raise ImportError("Unable to load format class '{}' ({})".format(dotted_path, err)) + raise ImportError(f"Unable to load format class '{dotted_path}' ({err})") class FormatDescriptorBase: diff --git a/src/tablib/formats/_html.py b/src/tablib/formats/_html.py index bfb096e..8aaf882 100644 --- a/src/tablib/formats/_html.py +++ b/src/tablib/formats/_html.py @@ -55,7 +55,7 @@ class HTMLFormat: for i, dset in enumerate(databook._datasets): title = (dset.title if dset.title else 'Set %s' % (i)) - wrapper.write('<{}>{}\n'.format(cls.BOOK_ENDINGS, title, cls.BOOK_ENDINGS)) + wrapper.write(f'<{cls.BOOK_ENDINGS}>{title}\n') wrapper.write(dset.html) wrapper.write('\n') diff --git a/src/tablib/formats/_jira.py b/src/tablib/formats/_jira.py index a4efc43..9bb62e7 100644 --- a/src/tablib/formats/_jira.py +++ b/src/tablib/formats/_jira.py @@ -21,7 +21,7 @@ class JIRAFormat: header = cls._get_header(dataset.headers) if dataset.headers else '' body = cls._get_body(dataset) - return '{}\n{}'.format(header, body) if header else body + return f'{header}\n{body}' if header else body @classmethod def _get_body(cls, dataset): diff --git a/src/tablib/formats/_xls.py b/src/tablib/formats/_xls.py index b4e7eb2..609810e 100644 --- a/src/tablib/formats/_xls.py +++ b/src/tablib/formats/_xls.py @@ -3,11 +3,12 @@ from io import BytesIO -import tablib import xlrd import xlwt from xlrd.xldate import xldate_as_datetime +import tablib + # special styles wrap = xlwt.easyxf("alignment: wrap on") bold = xlwt.easyxf("font: bold on") diff --git a/src/tablib/formats/_xlsx.py b/src/tablib/formats/_xlsx.py index e740373..e2a3fde 100644 --- a/src/tablib/formats/_xlsx.py +++ b/src/tablib/formats/_xlsx.py @@ -3,13 +3,14 @@ from io import BytesIO -import tablib from openpyxl.reader.excel import ExcelReader, load_workbook from openpyxl.styles import Alignment, Font from openpyxl.utils import get_column_letter from openpyxl.workbook import Workbook from openpyxl.writer.excel import ExcelWriter +import tablib + class XLSXFormat: title = 'xlsx' @@ -114,7 +115,7 @@ class XLSXFormat: row_number = i + 1 for j, col in enumerate(row): col_idx = get_column_letter(j + 1) - cell = ws['{}{}'.format(col_idx, row_number)] + cell = ws[f'{col_idx}{row_number}'] # bold headers if (row_number == 1) and dataset.headers: diff --git a/src/tablib/formats/_yaml.py b/src/tablib/formats/_yaml.py index f8f2dff..1b653d7 100644 --- a/src/tablib/formats/_yaml.py +++ b/src/tablib/formats/_yaml.py @@ -1,9 +1,10 @@ """ Tablib - YAML Support. """ -import tablib import yaml +import tablib + class YAMLFormat: title = 'yaml' diff --git a/src/tablib/packages/dbfpy/fields.py b/src/tablib/packages/dbfpy/fields.py index 8c7d022..49db171 100644 --- a/src/tablib/packages/dbfpy/fields.py +++ b/src/tablib/packages/dbfpy/fields.py @@ -315,7 +315,7 @@ class DbfLogicalFieldDef(DbfFieldDef): return False if value in "YyTt": return True - raise ValueError("[{}] Invalid logical value {!r}".format(self.name, value)) + raise ValueError(f"[{self.name}] Invalid logical value {value!r}") def encodeValue(self, value): """Return a character from the "TF?" set. diff --git a/tests/test_tablib.py b/tests/test_tablib.py index d78bfa7..b13d17c 100755 --- a/tests/test_tablib.py +++ b/tests/test_tablib.py @@ -11,8 +11,9 @@ from io import BytesIO, StringIO from pathlib import Path from uuid import uuid4 -import tablib from MarkupPy import markup + +import tablib from tablib.core import Row, detect_format from tablib.exceptions import UnsupportedFormat from tablib.formats import registry diff --git a/tox.ini b/tox.ini index 149fcb8..0934173 100644 --- a/tox.ini +++ b/tox.ini @@ -4,12 +4,14 @@ minversion = 2.4 envlist = docs lint - py{35,36,37,38} + py{36,37,38,39} [testenv] deps = -rtests/requirements.txt extras = pandas +passenv = + FORCE_COLOR commands = pytest {posargs:tests} @@ -35,4 +37,3 @@ skip_install = true [flake8] exclude = .tox -ignore=E501,E127,E128,E124