From c8a36c803450791ab17f56d5c77fa9f2a42c771f Mon Sep 17 00:00:00 2001 From: Marko Ristin Date: Tue, 13 Oct 2020 22:48:47 +0200 Subject: [PATCH] Added continuous integration This patch adds continuous integration based on GitHub workflows. While we are at it: * Added support for Python 3.6, 3.7 and 3.8 * Updated mypy and pylint to 0.790 and 2.6.0, respectively * Added isort, coverage and pydocstyle to checks * Introduced getters for ``path`` so that mypy correctly assumes it is a non-None --- .github/workflows/check-pull-request.yml | 47 +++++ .github/workflows/check-push.yml | 35 ++++ .gitignore | 2 + README.rst | 18 +- precommit.py | 249 +++++++---------------- setup.py | 20 +- temppathlib/__init__.py | 101 +++++---- tests/__init__.py | 1 + tests/test_temppathlib.py | 18 +- tox.ini | 10 +- 10 files changed, 274 insertions(+), 227 deletions(-) create mode 100644 .github/workflows/check-pull-request.yml create mode 100644 .github/workflows/check-push.yml create mode 100644 tests/__init__.py diff --git a/.github/workflows/check-pull-request.yml b/.github/workflows/check-pull-request.yml new file mode 100644 index 0000000..ef4db2c --- /dev/null +++ b/.github/workflows/check-pull-request.yml @@ -0,0 +1,47 @@ +name: Check-pull-request + +on: [pull_request] + +jobs: + Execute: + runs-on: ubuntu-latest + strategy: + matrix: + python-version: [3.5, 3.6, 3.7, 3.8] + + steps: + - uses: actions/checkout@master + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + + - name: Install dependencies + run: | + python3 -m pip install --upgrade pip + pip3 install --upgrade coveralls + pip3 install -e .[dev] + + - name: Run checks + run: ./precommit.py + + - name: Upload Coverage + run: coveralls + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + COVERALLS_FLAG_NAME: ${{ matrix.python-version }} + COVERALLS_PARALLEL: true + + Finish-Coveralls: + name: Finish Coveralls + needs: Execute + runs-on: ubuntu-latest + container: python:3-slim + steps: + - name: Finish Coveralls + run: | + pip3 install --upgrade coveralls + coveralls --finish + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/check-push.yml b/.github/workflows/check-push.yml new file mode 100644 index 0000000..3a3c63b --- /dev/null +++ b/.github/workflows/check-push.yml @@ -0,0 +1,35 @@ +name: Check-push + +on: + push: + branches: + - master + +jobs: + Execute: + runs-on: ubuntu-latest + strategy: + matrix: + python-version: [3.5, 3.6, 3.7, 3.8] + + steps: + - uses: actions/checkout@master + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + + - name: Install dependencies + run: | + python3 -m pip install --upgrade pip + pip3 install -e .[dev] + pip3 install coveralls + + - name: Run checks + run: ./precommit.py + + - name: Upload coverage to coveralls + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: coveralls diff --git a/.gitignore b/.gitignore index 8258f5e..a8cb24e 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,5 @@ venv3 *.egg-info .tox dist/ +venv/ +.coverage diff --git a/README.rst b/README.rst index 2183aa3..01d5d0b 100644 --- a/README.rst +++ b/README.rst @@ -1,7 +1,23 @@ temppathlib =========== -temppathlib provides wrappers around ``tempfile`` so that you can directly use them together with ``pathlib`` module. +.. image:: https://github.com/Parquery/temppathlib/workflows/Check-push/badge.svg + :target: https://github.com/Parquery/temppathlib/actions?query=workflow%3ACheck-push + :alt: Check status + +.. image:: https://coveralls.io/repos/github/Parquery/temppathlib/badge.svg?branch=master + :target: https://coveralls.io/github/Parquery/temppathlib + :alt: Test coverage + +.. image:: https://badge.fury.io/py/temppathlib.svg + :target: https://pypi.org/project/temppathlib/ + :alt: PyPI - version + +.. image:: https://img.shields.io/pypi/pyversions/temppathlib.svg + :target: https://pypi.org/project/temppathlib/ + :alt: PyPI - Python Version + +Temppathlib provides wrappers around ``tempfile`` so that you can directly use them together with ``pathlib`` module. We found it cumbersome to convert ``tempfile`` objects manually to ``pathlib.Path`` whenever we needed a temporary file. diff --git a/precommit.py b/precommit.py index 6a949b2..4dce949 100755 --- a/precommit.py +++ b/precommit.py @@ -1,202 +1,105 @@ #!/usr/bin/env python3 -""" -Runs precommit checks on the repository. -""" +"""Run precommit checks on the repository.""" import argparse -import concurrent.futures -import hashlib import os import pathlib +import re import subprocess import sys -from typing import List, Union, Tuple # pylint: disable=unused-import -import yapf.yapflib.yapf_api - - -def compute_hash(text: str) -> str: - """ - :param text: to hash - :return: hash digest - """ - md5 = hashlib.md5() - md5.update(text.encode()) - return md5.hexdigest() - - -class Hasher: - """ - Hashes the source code files and reports if they differed to one of the previous hashings. - """ - - def __init__(self, source_dir: pathlib.Path, hash_dir: pathlib.Path) -> None: - self.source_dir = source_dir - self.hash_dir = hash_dir - - def __hash_dir(self, path: pathlib.Path) -> pathlib.Path: - """ - :param path: to a source file - :return: path to the file holding the hash of the source text - """ - if self.source_dir not in path.parents: - raise ValueError("Expected the path to be beneath the source directory {!r}, got: {!r}".format( - str(self.source_dir), str(path))) - - return self.hash_dir / path.relative_to(self.source_dir).parent / path.name - - def hash_differs(self, path: pathlib.Path) -> bool: - """ - :param path: to the source file - :return: True if the hash of the content differs to one of the previous hashings. - """ - hash_dir = self.__hash_dir(path=path) - - if not hash_dir.exists(): - return True - - prev_hashes = set([pth.name for pth in hash_dir.iterdir()]) - - new_hsh = compute_hash(text=path.read_text()) - - return not new_hsh in prev_hashes - - def update_hash(self, path: pathlib.Path) -> None: - """ - Hashes the file content and stores it on disk. - - :param path: to the source file - :return: - """ - hash_dir = self.__hash_dir(path=path) - hash_dir.mkdir(exist_ok=True, parents=True) - - new_hsh = compute_hash(text=path.read_text()) - - pth = hash_dir / new_hsh - pth.write_text('passed') +def main() -> int: + """Execute the main routine.""" + parser = argparse.ArgumentParser() + parser.add_argument( + "--overwrite", + help="Overwrites the unformatted source files with the " + "well-formatted code in place. If not set, " + "an exception is raised if any of the files do not conform " + "to the style guide.", + action='store_true') -def check(path: pathlib.Path, py_dir: pathlib.Path, overwrite: bool) -> Union[None, str]: - """ - Runs all the checks on the given file. + args = parser.parse_args() - :param path: to the source file - :param py_dir: path to the source files - :param overwrite: if True, overwrites the source file in place instead of reporting that it was not well-formatted. - :return: None if all checks passed. Otherwise, an error message. - """ - style_config = py_dir / 'style.yapf' + overwrite = bool(args.overwrite) - report = [] + repo_root = pathlib.Path(__file__).parent - # yapf - if not overwrite: - formatted, _, changed = yapf.yapflib.yapf_api.FormatFile( - filename=str(path), style_config=str(style_config), print_diff=True) + # yapf: disable + source_files = ( + sorted((repo_root / "temppathlib").glob("**/*.py")) + + sorted((repo_root / "tests").glob("**/*.py"))) + # yapf: enable - if changed: - report.append("Failed to yapf {}:\n{}".format(path, formatted)) + if overwrite: + print('Removing trailing whitespace...') + for pth in source_files: + pth.write_text(re.sub(r'[ \t]+$', '', pth.read_text(), flags=re.MULTILINE)) + + print("YAPF'ing...") + yapf_targets = ["tests", "temppathlib", "setup.py", "precommit.py"] + if overwrite: + # yapf: disable + subprocess.check_call( + ["yapf", "--in-place", "--style=style.yapf", "--recursive"] + + yapf_targets, + cwd=str(repo_root)) + # yapf: enable else: - yapf.yapflib.yapf_api.FormatFile(filename=str(path), style_config=str(style_config), in_place=True) - - # mypy - env = os.environ.copy() - env['PYTHONPATH'] = ":".join([py_dir.as_posix(), env.get("PYTHONPATH", "")]) - - proc = subprocess.Popen( - ['mypy', str(path), '--ignore-missing-imports'], - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - env=env, - universal_newlines=True) - stdout, stderr = proc.communicate() - if proc.returncode != 0: - report.append("Failed to mypy {}:\nOutput:\n{}\n\nError:\n{}".format(path, stdout, stderr)) - - # pylint - proc = subprocess.Popen( - ['pylint', str(path), '--rcfile={}'.format(py_dir / 'pylint.rc')], - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - universal_newlines=True) - - stdout, stderr = proc.communicate() - if proc.returncode != 0: - report.append("Failed to pylint {}:\nOutput:\n{}\n\nError:\n{}".format(path, stdout, stderr)) + # yapf: disable + subprocess.check_call( + ["yapf", "--diff", "--style=style.yapf", "--recursive"] + + yapf_targets, + cwd=str(repo_root)) + # yapf: enable - if len(report) > 0: - return "\n".join(report) - - return None + print("Mypy'ing...") + subprocess.check_call(["mypy", "--strict", "temppathlib", "tests"], cwd=str(repo_root)) + print("Isort'ing...") + # yapf: disable + isort_files = map(str, source_files) + # yapf: enable -def main() -> int: - """" - Main routine - """ - # pylint: disable=too-many-locals - parser = argparse.ArgumentParser() - parser.add_argument( - "--overwrite", - help="Overwrites the unformatted source files with the well-formatted code in place. " - "If not set, an exception is raised if any of the files do not conform to the style guide.", - action='store_true') + # yapf: disable + subprocess.check_call( + ["isort", "--project", "temppathlib", '--line-width', '120'] + + ([] if overwrite else ['--check-only']) + + [str(pth) for pth in source_files]) + # yapf: enable - parser.add_argument("--all", help="checks all the files even if they didn't change", action='store_true') + print("Pydocstyle'ing...") + subprocess.check_call(["pydocstyle", "temppathlib"], cwd=str(repo_root)) - args = parser.parse_args() + print("Pylint'ing...") + subprocess.check_call(["pylint", "--rcfile=pylint.rc", "tests", "temppathlib"], cwd=str(repo_root)) - overwrite = bool(args.overwrite) - check_all = bool(args.all) + print("Testing...") + env = os.environ.copy() + env['ICONTRACT_SLOW'] = 'true' - py_dir = pathlib.Path(__file__).parent + # yapf: disable + subprocess.check_call( + ["coverage", "run", + "--source", "temppathlib", + "-m", "unittest", "discover", "tests"], + cwd=str(repo_root), + env=env) + # yapf: enable - hash_dir = py_dir / '.precommit_hashes' - hash_dir.mkdir(exist_ok=True) + subprocess.check_call(["coverage", "report"]) - hasher = Hasher(source_dir=py_dir, hash_dir=hash_dir) + print("Doctesting...") + doctest_files = ([repo_root / "README.rst"] + sorted((repo_root / "temppathlib").glob("**/*.py"))) - # yapf: disable - pths = sorted( - list(py_dir.glob("*.py")) + - list((py_dir / 'tests').glob("*.py")) - ) - # yapf: enable + for pth in doctest_files: + subprocess.check_call([sys.executable, "-m", "doctest", str(pth)]) - # see which files changed: - pending_pths = [] # type: List[pathlib.Path] + print("Checking setup.py sdist ...") + subprocess.check_call([sys.executable, "setup.py", "sdist"], cwd=str(repo_root)) - if check_all: - pending_pths = pths - else: - for pth in pths: - if hasher.hash_differs(path=pth): - pending_pths.append(pth) - - print("There are {} file(s) that need to be individually checked...".format(len(pending_pths))) - - success = True - - futures_paths = [] # type: List[Tuple[concurrent.futures.Future, pathlib.Path]] - with concurrent.futures.ThreadPoolExecutor() as executor: - for pth in pending_pths: - future = executor.submit(fn=check, path=pth, py_dir=py_dir, overwrite=overwrite) - futures_paths.append((future, pth)) - - for future, pth in futures_paths: - report = future.result() - if report is None: - print("Passed all checks: {}".format(pth)) - hasher.update_hash(path=pth) - else: - print("One or more checks failed for {}:\n{}".format(pth, report)) - success = False - - success = subprocess.call(['python3', '-m', 'unittest', 'discover', (py_dir / 'tests').as_posix()]) == 0 and success - - if not success: - print("One or more checks failed.") - return 1 + print("Checking with twine...") + subprocess.check_call(["twine", "check", "dist/*"], cwd=str(repo_root)) return 0 diff --git a/setup.py b/setup.py index 4128767..2e2aa34 100644 --- a/setup.py +++ b/setup.py @@ -18,23 +18,37 @@ setup( name='temppathlib', version='1.0.3', - description='Wraps tempfile to give you pathlib.Path.', + description='Wrap tempfile to give you pathlib.Path.', long_description=long_description, url='https://github.com/Parquery/temppathlib', author='Marko Ristin', author_email='marko@parquery.com', classifiers=[ + # yapf: disable 'Development Status :: 5 - Production/Stable', 'Intended Audience :: Developers', 'License :: OSI Approved :: MIT License', 'Programming Language :: Python :: 3.5', + 'Programming Language :: Python :: 3.6', + 'Programming Language :: Python :: 3.7', + 'Programming Language :: Python :: 3.8', + # yapf: enable ], keywords='tempfile pathlib temporary file directory mkdtemp mkstemp', packages=find_packages(exclude=['tests']), install_requires=None, extras_require={ - 'dev': ['mypy==0.570', 'pylint==1.8.2', 'yapf==0.20.2', 'tox>=3.0.0'], - 'test': ['tox>=3.0.0'] + 'dev': [ + # yapf: disable + 'mypy==0.790', + 'pylint==2.6.0', + 'yapf==0.20.2', + 'tox>=3,<4', + 'coverage>=5,<6', + 'pydocstyle>=5,<6', + 'twine' + # yapf: enable + ], }, py_modules=['temppathlib'], package_data={"temppathlib": ["py.typed"]}, diff --git a/temppathlib/__init__.py b/temppathlib/__init__.py index f2035d0..06a7497 100644 --- a/temppathlib/__init__.py +++ b/temppathlib/__init__.py @@ -1,17 +1,16 @@ -""" wraps tempfile to give you pathlib.Path. """ +"""Wrap tempfile to give you pathlib.Path.""" import pathlib import shutil import tempfile -from typing import Union, Optional, IO, Any # pylint: disable=unused-import +from typing import IO, Any, Optional, Union # pylint: disable=unused-import class removing_tree: # pylint: disable=invalid-name - """ - checks if the path exists, and if it does, calls shutil.rmtree on it. - """ + """Check if the path exists, and if it does, calls shutil.rmtree on it.""" def __init__(self, path: Union[str, pathlib.Path]) -> None: + """Initialize with the given value.""" if isinstance(path, str): self.path = pathlib.Path(path) elif isinstance(path, pathlib.Path): @@ -20,17 +19,20 @@ def __init__(self, path: Union[str, pathlib.Path]) -> None: raise ValueError("Unexpected type of 'path': {}".format(type(path))) def __enter__(self) -> pathlib.Path: + """Give back the path that will be removed.""" return self.path - def __exit__(self, exc_type, exc_val, exc_tb) -> None: + def __exit__(self, exc_type, exc_val, exc_tb) -> None: # type: ignore + """Remove the path if it exists.""" if self.path.exists(): shutil.rmtree(str(self.path)) class TmpDirIfNecessary: """ - either forwards the directory path (if defined) or creates a temporary directory and deletes it on exit - if dont_delete_tmp_dir is False. + Forward the directory path (if defined) or create a temporary directory. + + If dont_delete_tmp_dir is set to True, the temporary directory is not deleted on exit. The directory (be it a temporary or not) is created on enter. If the path was not specified (and a temporary directory needs to be created), its name is generated only on enter. @@ -41,6 +43,8 @@ def __init__(self, base_tmp_dir: Union[None, str, pathlib.Path] = None, dont_delete_tmp_dir: bool = False) -> None: """ + Initialize with the given values. + :param path: provided path to the directory; if specified, no temporary directory is created. :param base_tmp_dir: parent directory of the temporary directories; if not set, the default is used (usually '/tmp'). This path is only used if a temporary directory needs to be created @@ -59,14 +63,14 @@ def __init__(self, else: raise ValueError("Unexpected type of 'base_tmp_dir': {}".format(type(base_tmp_dir))) - self.path = None # type: Optional[pathlib.Path] + self._path = None # type: Optional[pathlib.Path] if path is None: pass elif isinstance(path, str): - self.path = pathlib.Path(path) + self._path = pathlib.Path(path) elif isinstance(path, pathlib.Path): - self.path = path + self._path = path else: raise ValueError("Unexpected type for the argument `path`: {}".format(type(path))) @@ -76,28 +80,39 @@ def __init__(self, self.exited = False - def __enter__(self): + @property + def path(self) -> pathlib.Path: + """Get the underlying path or raise if the path has not been set.""" + if self._path is None: + raise RuntimeError("The _path has not been set. " + "Are you using {} outside of the context management?".format(self.__class__.__name__)) + + return self._path + + def __enter__(self) -> 'TmpDirIfNecessary': + """Create the temporary directory if necessary.""" if self.exited: raise RuntimeError("Already exited") - if self.path is None: + if self._path is None: if self.base_tmp_dir is None: - self.path = pathlib.Path(tempfile.mkdtemp()) + self._path = pathlib.Path(tempfile.mkdtemp()) else: - self.path = pathlib.Path(tempfile.mkdtemp(dir=str(self.base_tmp_dir))) + self._path = pathlib.Path(tempfile.mkdtemp(dir=str(self.base_tmp_dir))) else: - self.path.mkdir(exist_ok=True, parents=True) + self._path.mkdir(exist_ok=True, parents=True) return self - def __exit__(self, exc_type, exc_val, exc_tb): + def __exit__(self, exc_type, exc_val, exc_tb): # type: ignore + """Remove the directory if dont_delete has not been set.""" if self.__use_tmp_dir and not self.dont_delete: - shutil.rmtree(str(self.path)) + shutil.rmtree(str(self._path)) class TemporaryDirectory: """ - creates a temporary directory and deletes it on exit. + Create a temporary directory and deletes it on exit. The path to the temporary directory is generated and the directory is created only on __enter__. """ @@ -107,12 +122,14 @@ def __init__(self, prefix: Optional[str] = None, dont_delete: bool = False) -> None: """ + Initialize with the given values. + :param base_tmp_dir: if specified, this directory will be used as the parent of the temporary directory. :param prefix: if specified, the prefix of the directory name :param dont_delete: if set, the directory is not deleted upon close(). """ self.exited = False - self.path = None # type: Optional[pathlib.Path] + self._path = None # type: Optional[pathlib.Path] if base_tmp_dir is None: self.base_tmp_dir = base_tmp_dir @@ -126,49 +143,59 @@ def __init__(self, self.prefix = prefix self.dont_delete = dont_delete - def __enter__(self): + def __enter__(self) -> 'TemporaryDirectory': + """Create the temporary directory.""" if self.exited: raise RuntimeError("Already exited") - base_tmp_dir = self.base_tmp_dir.as_posix() if self.base_tmp_dir is not None else None - self.path = pathlib.Path(tempfile.mkdtemp(prefix=self.prefix, dir=base_tmp_dir)) + base_tmp_dir = str(self.base_tmp_dir) if self.base_tmp_dir is not None else None + self._path = pathlib.Path(tempfile.mkdtemp(prefix=self.prefix, dir=base_tmp_dir)) return self + @property + def path(self) -> pathlib.Path: + """Get the underlying path or raise if the path has not been set.""" + if self._path is None: + raise RuntimeError("The _path has not been set. " + "Are you using {} outside of the context management?".format(self.__class__.__name__)) + + return self._path + def close(self) -> None: """ - closes the temporary directory. + Close the temporary directory. If already closed, does nothing. If dont_delete not set, deletes the temporary directory if it exists. - :return: """ if not self.exited: - if not self.dont_delete and self.path is not None and self.path.exists(): - shutil.rmtree(str(self.path)) + if not self.dont_delete and self._path is not None and self._path.exists(): + shutil.rmtree(str(self._path)) self.exited = True - def __exit__(self, exc_type, exc_val, exc_tb): + def __exit__(self, exc_type, exc_val, exc_tb): # type: ignore + """Close the temporary directory upon exit.""" self.close() class NamedTemporaryFile: - """ - wraps tempfile.NamedTemporaryFile with pathlib.Path. - """ + """Wrap tempfile.NamedTemporaryFile with pathlib.Path.""" def __init__( self, - mode='w+b', + mode: str = 'w+b', buffering: int = -1, encoding: Optional[str] = None, - newline=None, + newline: Optional[str] = None, suffix: Optional[str] = None, prefix: Optional[str] = None, dir: Optional[pathlib.Path] = None, # pylint: disable=redefined-builtin delete: bool = True) -> None: """ + Initialize with the given values. + The description of parameters is copied from the tempfile.NamedTemporaryFile docstring. :param mode: the mode argument to io.open (default "w+b") @@ -194,7 +221,7 @@ def __init__( newline=newline, suffix=suffix, prefix=prefix, - dir=dir.as_posix() if dir is not None else None, + dir=str(dir) if dir is not None else None, delete=delete) self.path = pathlib.Path(self.__tmpfile.name) @@ -204,11 +231,13 @@ def __init__( self.delete = delete def close(self) -> None: - """ forwards close() to the underlying temporary file. """ + """Forward close request to the underlying temporary file.""" self.__tmpfile.close() def __enter__(self) -> 'NamedTemporaryFile': + """Return this object; no further action is performed.""" return self - def __exit__(self, exc_type, exc_val, exc_tb) -> None: + def __exit__(self, exc_type, exc_val, exc_tb) -> None: # type: ignore + """Close the temporary file.""" self.close() diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..c780924 --- /dev/null +++ b/tests/__init__.py @@ -0,0 +1 @@ +"""Test temppathlib.""" diff --git a/tests/test_temppathlib.py b/tests/test_temppathlib.py index cc4c74f..446414f 100644 --- a/tests/test_temppathlib.py +++ b/tests/test_temppathlib.py @@ -14,7 +14,7 @@ class TestRemovingTree(unittest.TestCase): - def test_that_it_works(self): + def test_that_it_works(self) -> None: tmp_dir = pathlib.Path(tempfile.mkdtemp()) try: @@ -36,7 +36,7 @@ def test_that_it_works(self): if tmp_dir.exists(): shutil.rmtree(str(tmp_dir)) - def test_no_enter(self): + def test_no_enter(self) -> None: tmp_dir = pathlib.Path(tempfile.mkdtemp()) try: @@ -66,7 +66,7 @@ def test_no_enter(self): class TestTmpDirIfNecessary(unittest.TestCase): - def test_with_path_str(self): + def test_with_path_str(self) -> None: basedir = pathlib.Path(tempfile.mkdtemp()) try: @@ -79,7 +79,7 @@ def test_with_path_str(self): finally: shutil.rmtree(str(basedir)) - def test_with_base_tmp_dir(self): + def test_with_base_tmp_dir(self) -> None: basedir = pathlib.Path(tempfile.mkdtemp()) try: @@ -96,11 +96,11 @@ def test_with_base_tmp_dir(self): class TestTemporaryDirectory(unittest.TestCase): - def test_that_it_works(self): + def test_that_it_works(self) -> None: tmp_dir = pathlib.Path(tempfile.mkdtemp()) try: - another_tmp_dir_pth = pathlib.Path() + another_tmp_dir_pth = None # type: Optional[pathlib.Path] with temppathlib.TemporaryDirectory(base_tmp_dir=tmp_dir) as another_tmp_dir: another_tmp_dir_pth = copy.copy(another_tmp_dir.path) @@ -112,13 +112,13 @@ def test_that_it_works(self): if tmp_dir.exists(): shutil.rmtree(str(tmp_dir)) - def test_with_prefix(self): + def test_with_prefix(self) -> None: with temppathlib.TemporaryDirectory(prefix='some-prefix') as tmp_dir: self.assertTrue(tmp_dir.path.name.startswith('some-prefix')) class TestNamedTemporaryFile(unittest.TestCase): - def test_that_it_works(self): + def test_that_it_works(self) -> None: pth = None # type: Optional[pathlib.Path] with temppathlib.NamedTemporaryFile() as tmp: self.assertIsNotNone(tmp.file) @@ -128,7 +128,7 @@ def test_that_it_works(self): self.assertFalse(pth.exists()) - def test_with_dir(self): + def test_with_dir(self) -> None: with temppathlib.TemporaryDirectory() as tmp_dir: with temppathlib.NamedTemporaryFile(dir=tmp_dir.path) as tmp: self.assertIsNotNone(tmp.file) diff --git a/tox.ini b/tox.ini index 42e6a92..0f83aee 100644 --- a/tox.ini +++ b/tox.ini @@ -1,11 +1,11 @@ [tox] -envlist = py35 +envlist = py35,py36,py37,py38 [testenv] -deps = - mypy==0.570 - pylint==1.8.2 - yapf==0.20.2 +deps = .[dev] changedir = {envtmpdir} commands = python3 {toxinidir}/precommit.py + +setenv = + COVERAGE_FILE={envbindir}/.coverage