From 87b75267f8a3c184f0c5e2aa3a4485f217d0a08d Mon Sep 17 00:00:00 2001 From: David Tucker Date: Fri, 12 Mar 2021 09:20:27 -0800 Subject: [PATCH 1/7] Set testpaths for pytest --- tox.ini | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 4394836..b456cc1 100644 --- a/tox.ini +++ b/tox.ini @@ -137,7 +137,10 @@ deps = pexpect ~= 4.8.0 -commands = py.test -p no:mypy --cov pytest_mypy --cov-fail-under 100 --cov-report term-missing {posargs:-n auto} tests +commands = py.test -p no:mypy --cov pytest_mypy --cov-fail-under 100 --cov-report term-missing {posargs:-n auto} + +[pytest] +testpaths = tests [testenv:publish] passenv = TWINE_* From afbacf9e5b2ce6b09e20c6c701a19895108999a8 Mon Sep 17 00:00:00 2001 From: David Tucker Date: Fri, 12 Mar 2021 09:21:22 -0800 Subject: [PATCH 2/7] Allow running custom posargs without code coverage --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index b456cc1..61d32d4 100644 --- a/tox.ini +++ b/tox.ini @@ -137,7 +137,7 @@ deps = pexpect ~= 4.8.0 -commands = py.test -p no:mypy --cov pytest_mypy --cov-fail-under 100 --cov-report term-missing {posargs:-n auto} +commands = py.test -p no:mypy {posargs:--cov pytest_mypy --cov-fail-under 100 --cov-report term-missing -n auto} [pytest] testpaths = tests From f0403cf1cb049fd0a9ccdc30d4735170552584b2 Mon Sep 17 00:00:00 2001 From: David Tucker Date: Sun, 11 Oct 2020 10:12:01 -0700 Subject: [PATCH 3/7] Run mypy and bandit on setup.py --- setup.py | 2 +- tox.ini | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/setup.py b/setup.py index e19434e..7fbdb7b 100644 --- a/setup.py +++ b/setup.py @@ -4,7 +4,7 @@ import glob import os import codecs -from setuptools import setup, find_packages +from setuptools import setup, find_packages # type: ignore def read(fname): diff --git a/tox.ini b/tox.ini index 61d32d4..427c5a2 100644 --- a/tox.ini +++ b/tox.ini @@ -157,6 +157,6 @@ deps = flake8 ~= 3.8.3 mypy >= 0.790, < 0.800 commands = - bandit --recursive src + bandit --recursive src setup.py flake8 setup.py src tests - mypy src + mypy src setup.py From 1b7155bcf8741e288b5f0d9d7f0934f90c0b4a4e Mon Sep 17 00:00:00 2001 From: David Tucker Date: Sun, 11 Oct 2020 10:21:27 -0700 Subject: [PATCH 4/7] Add Black and flake8-commas --- setup.py | 75 +++++----- src/pytest_mypy.py | 106 +++++++------- tests/conftest.py | 2 +- tests/test_pytest_mypy.py | 287 +++++++++++++++++++++----------------- tox.ini | 11 +- 5 files changed, 256 insertions(+), 225 deletions(-) diff --git a/setup.py b/setup.py index 7fbdb7b..fee3c5d 100644 --- a/setup.py +++ b/setup.py @@ -9,58 +9,51 @@ def read(fname): file_path = os.path.join(os.path.dirname(__file__), fname) - return codecs.open(file_path, encoding='utf-8').read() + return codecs.open(file_path, encoding="utf-8").read() setup( - name='pytest-mypy', + name="pytest-mypy", use_scm_version=True, - author='Daniel Bader', - author_email='mail@dbader.org', - maintainer='David Tucker', - maintainer_email='david@tucker.name', - license='MIT', - url='https://github.com/dbader/pytest-mypy', - description='Mypy static type checker plugin for Pytest', - long_description=read('README.rst'), - long_description_content_type='text/x-rst', - packages=find_packages('src'), - package_dir={'': 'src'}, + author="Daniel Bader", + author_email="mail@dbader.org", + maintainer="David Tucker", + maintainer_email="david@tucker.name", + license="MIT", + url="https://github.com/dbader/pytest-mypy", + description="Mypy static type checker plugin for Pytest", + long_description=read("README.rst"), + long_description_content_type="text/x-rst", + packages=find_packages("src"), + package_dir={"": "src"}, py_modules=[ - os.path.splitext(os.path.basename(path))[0] - for path in glob.glob('src/*.py') - ], - python_requires='>=3.5', - setup_requires=[ - 'setuptools-scm>=3.5', + os.path.splitext(os.path.basename(path))[0] for path in glob.glob("src/*.py") ], + python_requires=">=3.5", + setup_requires=["setuptools-scm>=3.5"], install_requires=[ - 'attrs>=19.0', - 'filelock>=3.0', - 'pytest>=3.5', + "attrs>=19.0", + "filelock>=3.0", + "pytest>=3.5", 'mypy>=0.500; python_version<"3.8"', 'mypy>=0.700; python_version>="3.8" and python_version<"3.9"', 'mypy>=0.780; python_version>="3.9"', ], classifiers=[ - 'Development Status :: 4 - Beta', - 'Framework :: Pytest', - 'Intended Audience :: Developers', - 'Topic :: Software Development :: Testing', - 'Programming Language :: Python', - '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', - 'Programming Language :: Python :: Implementation :: CPython', - 'Operating System :: OS Independent', - 'License :: OSI Approved :: MIT License', + "Development Status :: 4 - Beta", + "Framework :: Pytest", + "Intended Audience :: Developers", + "Topic :: Software Development :: Testing", + "Programming Language :: Python", + "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", + "Programming Language :: Python :: Implementation :: CPython", + "Operating System :: OS Independent", + "License :: OSI Approved :: MIT License", ], - entry_points={ - 'pytest11': [ - 'mypy = pytest_mypy', - ], - }, + entry_points={"pytest11": ["mypy = pytest_mypy"]}, ) diff --git a/src/pytest_mypy.py b/src/pytest_mypy.py index 54ba108..497d626 100644 --- a/src/pytest_mypy.py +++ b/src/pytest_mypy.py @@ -12,12 +12,12 @@ mypy_argv = [] -nodeid_name = 'mypy' +nodeid_name = "mypy" def default_file_error_formatter(item, results, errors): """Create a string to be displayed when mypy finds errors in a file.""" - return '\n'.join(errors) + return "\n".join(errors) file_error_formatter = default_file_error_formatter @@ -25,19 +25,19 @@ def default_file_error_formatter(item, results, errors): def pytest_addoption(parser): """Add options for enabling and running mypy.""" - group = parser.getgroup('mypy') + group = parser.getgroup("mypy") + group.addoption("--mypy", action="store_true", help="run mypy on .py files") group.addoption( - '--mypy', action='store_true', - help='run mypy on .py files') - group.addoption( - '--mypy-ignore-missing-imports', action='store_true', - help="suppresses error messages about imports that cannot be resolved") + "--mypy-ignore-missing-imports", + action="store_true", + help="suppresses error messages about imports that cannot be resolved", + ) XDIST_WORKERINPUT_ATTRIBUTE_NAMES = ( - 'workerinput', + "workerinput", # xdist < 2.0.0: - 'slaveinput', + "slaveinput", ) @@ -76,12 +76,15 @@ def pytest_configure(config): # If xdist is enabled, then the results path should be exposed to # the workers so that they know where to read parsed results from. - if config.pluginmanager.getplugin('xdist'): + if config.pluginmanager.getplugin("xdist"): + class _MypyXdistPlugin: def pytest_configure_node(self, node): # xdist hook """Pass config._mypy_results_path to workers.""" - _get_xdist_workerinput(node)['_mypy_results_path'] = \ - node.config._mypy_results_path + _get_xdist_workerinput(node)[ + "_mypy_results_path" + ] = node.config._mypy_results_path + config.pluginmanager.register(_MypyXdistPlugin()) # pytest_terminal_summary cannot accept config before pytest 4.2. @@ -89,25 +92,22 @@ def pytest_configure_node(self, node): # xdist hook _pytest_terminal_summary_config = config config.addinivalue_line( - 'markers', - '{marker}: mark tests to be checked by mypy.'.format( - marker=MypyItem.MARKER, - ), + "markers", + "{marker}: mark tests to be checked by mypy.".format(marker=MypyItem.MARKER), ) - if config.getoption('--mypy-ignore-missing-imports'): - mypy_argv.append('--ignore-missing-imports') + if config.getoption("--mypy-ignore-missing-imports"): + mypy_argv.append("--ignore-missing-imports") def pytest_collect_file(path, parent): """Create a MypyFileItem for every file mypy should run on.""" - if path.ext in {'.py', '.pyi'} and any([ - parent.config.option.mypy, - parent.config.option.mypy_ignore_missing_imports, - ]): + if path.ext in {".py", ".pyi"} and any( + [parent.config.option.mypy, parent.config.option.mypy_ignore_missing_imports], + ): # Do not create MypyFile instance for a .py file if a # .pyi file with the same name already exists; # pytest will complain about duplicate modules otherwise - if path.ext == '.pyi' or not path.new(ext='.pyi').isfile(): + if path.ext == ".pyi" or not path.new(ext=".pyi").isfile(): return MypyFile.from_parent(parent=parent, fspath=path) return None @@ -120,7 +120,7 @@ class MypyFile(pytest.File): def from_parent(cls, *args, **kwargs): """Override from_parent for compatibility.""" # pytest.File.from_parent did not exist before pytest 5.4. - return getattr(super(), 'from_parent', cls)(*args, **kwargs) + return getattr(super(), "from_parent", cls)(*args, **kwargs) def collect(self): """Create a MypyFileItem for the File.""" @@ -128,9 +128,7 @@ def collect(self): # Since mypy might check files that were not collected, # pytest could pass even though mypy failed! # To prevent that, add an explicit check for the mypy exit status. - if not any( - isinstance(item, MypyStatusItem) for item in self.session.items - ): + if not any(isinstance(item, MypyStatusItem) for item in self.session.items): yield MypyStatusItem.from_parent( parent=self, name=nodeid_name + "-status", @@ -141,7 +139,7 @@ class MypyItem(pytest.Item): """A Mypy-related test Item.""" - MARKER = 'mypy' + MARKER = "mypy" def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) @@ -151,7 +149,7 @@ def __init__(self, *args, **kwargs): def from_parent(cls, *args, **kwargs): """Override from_parent for compatibility.""" # pytest.Item.from_parent did not exist before pytest 5.4. - return getattr(super(), 'from_parent', cls)(*args, **kwargs) + return getattr(super(), "from_parent", cls)(*args, **kwargs) def repr_failure(self, excinfo): """ @@ -193,7 +191,7 @@ def runtest(self): results = MypyResults.from_session(self.session) if results.status: raise MypyError( - 'mypy exited with status {status}.'.format( + "mypy exited with status {status}.".format( status=results.status, ), ) @@ -218,33 +216,32 @@ def dump(self, results_f: TextIO) -> None: return json.dump(vars(self), results_f) @classmethod - def load(cls, results_f: TextIO) -> 'MypyResults': + def load(cls, results_f: TextIO) -> "MypyResults": """Get results cached by dump().""" return cls(**json.load(results_f)) @classmethod def from_mypy( - cls, - items: List[MypyFileItem], - *, - opts: Optional[List[str]] = None - ) -> 'MypyResults': + cls, + items: List[MypyFileItem], + *, + opts: Optional[List[str]] = None # noqa: C816 + ) -> "MypyResults": """Generate results from mypy.""" if opts is None: opts = mypy_argv[:] abspath_errors = { - os.path.abspath(str(item.fspath)): [] - for item in items + os.path.abspath(str(item.fspath)): [] for item in items } # type: MypyResults._abspath_errors_type stdout, stderr, status = mypy.api.run(opts + list(abspath_errors)) unmatched_lines = [] - for line in stdout.split('\n'): + for line in stdout.split("\n"): if not line: continue - path, _, error = line.partition(':') + path, _, error = line.partition(":") abspath = os.path.abspath(path) try: abspath_errors[abspath].append(error) @@ -257,27 +254,26 @@ def from_mypy( stderr=stderr, status=status, abspath_errors=abspath_errors, - unmatched_stdout='\n'.join(unmatched_lines), + unmatched_stdout="\n".join(unmatched_lines), ) @classmethod - def from_session(cls, session) -> 'MypyResults': + def from_session(cls, session) -> "MypyResults": """Load (or generate) cached mypy results for a pytest session.""" results_path = ( session.config._mypy_results_path - if _is_master(session.config) else - _get_xdist_workerinput(session.config)['_mypy_results_path'] + if _is_master(session.config) + else _get_xdist_workerinput(session.config)["_mypy_results_path"] ) - with FileLock(results_path + '.lock'): + with FileLock(results_path + ".lock"): try: - with open(results_path, mode='r') as results_f: + with open(results_path, mode="r") as results_f: results = cls.load(results_f) except FileNotFoundError: - results = cls.from_mypy([ - item for item in session.items - if isinstance(item, MypyFileItem) - ]) - with open(results_path, mode='w') as results_f: + results = cls.from_mypy( + [item for item in session.items if isinstance(item, MypyFileItem)], + ) + with open(results_path, mode="w") as results_f: results.dump(results_f) return results @@ -293,15 +289,15 @@ def pytest_terminal_summary(terminalreporter): """Report stderr and unrecognized lines from stdout.""" config = _pytest_terminal_summary_config try: - with open(config._mypy_results_path, mode='r') as results_f: + with open(config._mypy_results_path, mode="r") as results_f: results = MypyResults.load(results_f) except FileNotFoundError: # No MypyItems executed. return if results.unmatched_stdout or results.stderr: - terminalreporter.section('mypy') + terminalreporter.section("mypy") if results.unmatched_stdout: - color = {'red': True} if results.status else {'green': True} + color = {"red": True} if results.status else {"green": True} terminalreporter.write_line(results.unmatched_stdout, **color) if results.stderr: terminalreporter.write_line(results.stderr, yellow=True) diff --git a/tests/conftest.py b/tests/conftest.py index bc711e5..694d7d5 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1 +1 @@ -pytest_plugins = 'pytester' +pytest_plugins = "pytester" diff --git a/tests/test_pytest_mypy.py b/tests/test_pytest_mypy.py index 29f9ea9..c364b59 100644 --- a/tests/test_pytest_mypy.py +++ b/tests/test_pytest_mypy.py @@ -13,25 +13,27 @@ ) def xdist_args(request): if request.param is None: - return ['-p', 'no:xdist'] - return ['-n', 'auto'] if request.param else [] + return ["-p", "no:xdist"] + return ["-n", "auto"] if request.param else [] -@pytest.mark.parametrize('pyfile_count', [1, 2]) +@pytest.mark.parametrize("pyfile_count", [1, 2]) def test_mypy_success(testdir, pyfile_count, xdist_args): """Verify that running on a module with no type errors passes.""" testdir.makepyfile( **{ - 'pyfile_' + str(pyfile_i): ''' + "pyfile_{0}".format( + pyfile_i, + ): """ def pyfunc(x: int) -> int: return x * 2 - ''' + """ for pyfile_i in range(pyfile_count) - } + }, ) result = testdir.runpytest_subprocess(*xdist_args) result.assert_outcomes() - result = testdir.runpytest_subprocess('--mypy', *xdist_args) + result = testdir.runpytest_subprocess("--mypy", *xdist_args) mypy_file_checks = pyfile_count mypy_status_check = 1 mypy_checks = mypy_file_checks + mypy_status_check @@ -46,22 +48,23 @@ def test_mypy_pyi(testdir, xdist_args): """ # The incorrect signature below should be ignored # as the .pyi file takes priority - testdir.makefile( - '.py', pyfile=''' + testdir.makepyfile( + pyfile=""" def pyfunc(x: int) -> str: return x * 2 - ''' + """, ) testdir.makefile( - '.pyi', pyfile=''' + ".pyi", + pyfile=""" def pyfunc(x: int) -> int: ... - ''' + """, ) result = testdir.runpytest_subprocess(*xdist_args) result.assert_outcomes() - result = testdir.runpytest_subprocess('--mypy', *xdist_args) + result = testdir.runpytest_subprocess("--mypy", *xdist_args) mypy_file_checks = 1 mypy_status_check = 1 mypy_checks = mypy_file_checks + mypy_status_check @@ -71,20 +74,20 @@ def pyfunc(x: int) -> int: ... def test_mypy_error(testdir, xdist_args): """Verify that running on a module with type errors fails.""" - testdir.makepyfile(''' - def pyfunc(x: int) -> str: - return x * 2 - ''') + testdir.makepyfile( + """ + def pyfunc(x: int) -> str: + return x * 2 + """, + ) result = testdir.runpytest_subprocess(*xdist_args) result.assert_outcomes() - result = testdir.runpytest_subprocess('--mypy', *xdist_args) + result = testdir.runpytest_subprocess("--mypy", *xdist_args) mypy_file_checks = 1 mypy_status_check = 1 mypy_checks = mypy_file_checks + mypy_status_check result.assert_outcomes(failed=mypy_checks) - result.stdout.fnmatch_lines([ - '2: error: Incompatible return value*', - ]) + result.stdout.fnmatch_lines(["2: error: Incompatible return value*"]) assert result.ret != 0 @@ -93,184 +96,216 @@ def test_mypy_ignore_missings_imports(testdir, xdist_args): Verify that --mypy-ignore-missing-imports causes mypy to ignore missing imports. """ - module_name = 'is_always_missing' - testdir.makepyfile(''' - try: - import {module_name} - except ImportError: - pass - '''.format(module_name=module_name)) - result = testdir.runpytest_subprocess('--mypy', *xdist_args) + module_name = "is_always_missing" + testdir.makepyfile( + """ + try: + import {module_name} + except ImportError: + pass + """.format( + module_name=module_name, + ), + ) + result = testdir.runpytest_subprocess("--mypy", *xdist_args) mypy_file_checks = 1 mypy_status_check = 1 mypy_checks = mypy_file_checks + mypy_status_check result.assert_outcomes(failed=mypy_checks) - result.stdout.fnmatch_lines([ - "2: error: Cannot find *module named '{module_name}'".format( - module_name=module_name, - ), - ]) - assert result.ret != 0 - result = testdir.runpytest_subprocess( - '--mypy-ignore-missing-imports', - *xdist_args + result.stdout.fnmatch_lines( + [ + "2: error: Cannot find *module named '{module_name}'".format( + module_name=module_name, + ), + ], ) + assert result.ret != 0 + result = testdir.runpytest_subprocess("--mypy-ignore-missing-imports", *xdist_args) result.assert_outcomes(passed=mypy_checks) assert result.ret == 0 def test_mypy_marker(testdir, xdist_args): """Verify that -m mypy only runs the mypy tests.""" - testdir.makepyfile(''' - def test_fails(): - assert False - ''') - result = testdir.runpytest_subprocess('--mypy', *xdist_args) + testdir.makepyfile( + """ + def test_fails(): + assert False + """, + ) + result = testdir.runpytest_subprocess("--mypy", *xdist_args) test_count = 1 mypy_file_checks = 1 mypy_status_check = 1 mypy_checks = mypy_file_checks + mypy_status_check result.assert_outcomes(failed=test_count, passed=mypy_checks) assert result.ret != 0 - result = testdir.runpytest_subprocess('--mypy', '-m', 'mypy', *xdist_args) + result = testdir.runpytest_subprocess("--mypy", "-m", "mypy", *xdist_args) result.assert_outcomes(passed=mypy_checks) assert result.ret == 0 def test_non_mypy_error(testdir, xdist_args): """Verify that non-MypyError exceptions are passed through the plugin.""" - message = 'This is not a MypyError.' - testdir.makepyfile(conftest=''' - def pytest_configure(config): - plugin = config.pluginmanager.getplugin('mypy') + message = "This is not a MypyError." + testdir.makepyfile( + conftest=""" + def pytest_configure(config): + plugin = config.pluginmanager.getplugin('mypy') - class PatchedMypyFileItem(plugin.MypyFileItem): - def runtest(self): - raise Exception('{message}') + class PatchedMypyFileItem(plugin.MypyFileItem): + def runtest(self): + raise Exception('{message}') - plugin.MypyFileItem = PatchedMypyFileItem - '''.format(message=message)) + plugin.MypyFileItem = PatchedMypyFileItem + """.format( + message=message, + ), + ) result = testdir.runpytest_subprocess(*xdist_args) result.assert_outcomes() - result = testdir.runpytest_subprocess('--mypy', *xdist_args) + result = testdir.runpytest_subprocess("--mypy", *xdist_args) mypy_file_checks = 1 # conftest.py mypy_status_check = 1 result.assert_outcomes( failed=mypy_file_checks, # patched to raise an Exception passed=mypy_status_check, # conftest.py has no type errors. ) - result.stdout.fnmatch_lines(['*' + message]) + result.stdout.fnmatch_lines(["*" + message]) assert result.ret != 0 def test_mypy_stderr(testdir, xdist_args): """Verify that stderr from mypy is printed.""" - stderr = 'This is stderr from mypy.' - testdir.makepyfile(conftest=''' - import mypy.api + stderr = "This is stderr from mypy." + testdir.makepyfile( + conftest=""" + import mypy.api - def _patched_run(*args, **kwargs): - return '', '{stderr}', 1 + def _patched_run(*args, **kwargs): + return '', '{stderr}', 1 - mypy.api.run = _patched_run - '''.format(stderr=stderr)) - result = testdir.runpytest_subprocess('--mypy', *xdist_args) + mypy.api.run = _patched_run + """.format( + stderr=stderr, + ), + ) + result = testdir.runpytest_subprocess("--mypy", *xdist_args) result.stdout.fnmatch_lines([stderr]) def test_mypy_unmatched_stdout(testdir, xdist_args): """Verify that unexpected output on stdout from mypy is printed.""" - stdout = 'This is unexpected output on stdout from mypy.' - testdir.makepyfile(conftest=''' - import mypy.api + stdout = "This is unexpected output on stdout from mypy." + testdir.makepyfile( + conftest=""" + import mypy.api - def _patched_run(*args, **kwargs): - return '{stdout}', '', 1 + def _patched_run(*args, **kwargs): + return '{stdout}', '', 1 - mypy.api.run = _patched_run - '''.format(stdout=stdout)) - result = testdir.runpytest_subprocess('--mypy', *xdist_args) + mypy.api.run = _patched_run + """.format( + stdout=stdout, + ), + ) + result = testdir.runpytest_subprocess("--mypy", *xdist_args) result.stdout.fnmatch_lines([stdout]) def test_api_mypy_argv(testdir, xdist_args): """Ensure that the plugin can be configured in a conftest.py.""" - testdir.makepyfile(conftest=''' - def pytest_configure(config): - plugin = config.pluginmanager.getplugin('mypy') - plugin.mypy_argv.append('--version') - ''') - result = testdir.runpytest_subprocess('--mypy', *xdist_args) + testdir.makepyfile( + conftest=""" + def pytest_configure(config): + plugin = config.pluginmanager.getplugin('mypy') + plugin.mypy_argv.append('--version') + """, + ) + result = testdir.runpytest_subprocess("--mypy", *xdist_args) assert result.ret == 0 def test_api_nodeid_name(testdir, xdist_args): """Ensure that the plugin can be configured in a conftest.py.""" - nodeid_name = 'UnmistakableNodeIDName' - testdir.makepyfile(conftest=''' - def pytest_configure(config): - plugin = config.pluginmanager.getplugin('mypy') - plugin.nodeid_name = '{}' - '''.format(nodeid_name)) - result = testdir.runpytest_subprocess('--mypy', '--verbose', *xdist_args) - result.stdout.fnmatch_lines(['*conftest.py::' + nodeid_name + '*']) + nodeid_name = "UnmistakableNodeIDName" + testdir.makepyfile( + conftest=""" + def pytest_configure(config): + plugin = config.pluginmanager.getplugin('mypy') + plugin.nodeid_name = '{}' + """.format( + nodeid_name, + ), + ) + result = testdir.runpytest_subprocess("--mypy", "--verbose", *xdist_args) + result.stdout.fnmatch_lines(["*conftest.py::" + nodeid_name + "*"]) assert result.ret == 0 def test_mypy_indirect(testdir, xdist_args): """Verify that uncollected files checked by mypy cause a failure.""" - testdir.makepyfile(bad=''' - def pyfunc(x: int) -> str: - return x * 2 - ''') - testdir.makepyfile(good=''' - import bad - ''') - result = testdir.runpytest_subprocess('--mypy', *xdist_args, 'good.py') + testdir.makepyfile( + bad=""" + def pyfunc(x: int) -> str: + return x * 2 + """, + ) + testdir.makepyfile( + good=""" + import bad + """, + ) + result = testdir.runpytest_subprocess("--mypy", *xdist_args, "good.py") assert result.ret != 0 def test_api_error_formatter(testdir, xdist_args): """Ensure that the plugin can be configured in a conftest.py.""" - testdir.makepyfile(bad=''' - def pyfunc(x: int) -> str: - return x * 2 - ''') - testdir.makepyfile(conftest=''' - def custom_file_error_formatter(item, results, errors): - return '\\n'.join( - '{path}:{error}'.format( - path=item.fspath, - error=error, + testdir.makepyfile( + bad=""" + def pyfunc(x: int) -> str: + return x * 2 + """, + ) + testdir.makepyfile( + conftest=""" + def custom_file_error_formatter(item, results, errors): + return '\\n'.join( + '{path}:{error}'.format( + path=item.fspath, + error=error, + ) + for error in errors ) - for error in errors - ) - - def pytest_configure(config): - plugin = config.pluginmanager.getplugin('mypy') - plugin.file_error_formatter = custom_file_error_formatter - ''') - result = testdir.runpytest_subprocess('--mypy', *xdist_args) - result.stdout.fnmatch_lines([ - '*/bad.py:2: error: Incompatible return value*', - ]) + + def pytest_configure(config): + plugin = config.pluginmanager.getplugin('mypy') + plugin.file_error_formatter = custom_file_error_formatter + """, + ) + result = testdir.runpytest_subprocess("--mypy", *xdist_args) + result.stdout.fnmatch_lines(["*/bad.py:2: error: Incompatible return value*"]) assert result.ret != 0 def test_setup_cfg(testdir, xdist_args): """Ensure that the plugin allows configuration with setup.cfg.""" - testdir.makefile('.cfg', setup=''' - [mypy] - disallow_untyped_defs = True - ''') - testdir.makepyfile(conftest=''' - def pyfunc(x): - return x * 2 - ''') - result = testdir.runpytest_subprocess('--mypy', *xdist_args) - result.stdout.fnmatch_lines([ - '1: error: Function is missing a type annotation', - ]) + testdir.makefile( + ".cfg", + setup=""" + [mypy] + disallow_untyped_defs = True + """, + ) + testdir.makepyfile( + conftest=""" + def pyfunc(x): + return x * 2 + """, + ) + result = testdir.runpytest_subprocess("--mypy", *xdist_args) + result.stdout.fnmatch_lines(["1: error: Function is missing a type annotation"]) assert result.ret != 0 diff --git a/tox.ini b/tox.ini index 427c5a2..cb0984c 100644 --- a/tox.ini +++ b/tox.ini @@ -154,9 +154,16 @@ commands = [testenv:static] deps = bandit ~= 1.6.2 + black ~= 20.8b flake8 ~= 3.8.3 + flake8-commas ~= 2.0.0 mypy >= 0.790, < 0.800 commands = - bandit --recursive src setup.py - flake8 setup.py src tests + black --check src setup.py tests + flake8 src setup.py tests mypy src setup.py + bandit --recursive src setup.py + +[flake8] +max-line-length = 88 +extend-ignore = E203 From d4ac36dcd2b463299276f29852d8427bf6732415 Mon Sep 17 00:00:00 2001 From: David Tucker Date: Sun, 14 Mar 2021 10:52:24 -0700 Subject: [PATCH 5/7] Use build instead of pep517 to publish --- tox.ini | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tox.ini b/tox.ini index cb0984c..69100ba 100644 --- a/tox.ini +++ b/tox.ini @@ -145,10 +145,10 @@ testpaths = tests [testenv:publish] passenv = TWINE_* deps = - pep517 ~= 0.8.0 + build ~= 0.3.0 twine ~= 3.2.0 commands = - {envpython} -m pep517.build --out-dir {distdir} . + {envpython} -m build --outdir {distdir} . twine {posargs:check} {distdir}/* [testenv:static] From d3181c3acc0c5d2e21144e274525e7fdf7176c0d Mon Sep 17 00:00:00 2001 From: David Tucker Date: Sun, 14 Mar 2021 10:56:57 -0700 Subject: [PATCH 6/7] Update build deps --- pyproject.toml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 59ecba1..0a59365 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,3 +1,5 @@ [build-system] -requires = ['setuptools ~= 45.2.0', 'setuptools-scm ~= 3.5.0', 'wheel ~= 0.34.0'] +requires = ['setuptools ~= 50.3.0', 'setuptools-scm[toml] ~= 5.0.0', 'wheel ~= 0.36.0'] build-backend = 'setuptools.build_meta' + +[tool.setuptools_scm] From 98429f77c12b4e71ef64e45e5a499239d7b493e7 Mon Sep 17 00:00:00 2001 From: David Tucker Date: Sun, 14 Mar 2021 10:58:53 -0700 Subject: [PATCH 7/7] Update static and publish deps --- tox.ini | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tox.ini b/tox.ini index 69100ba..9435434 100644 --- a/tox.ini +++ b/tox.ini @@ -146,18 +146,18 @@ testpaths = tests passenv = TWINE_* deps = build ~= 0.3.0 - twine ~= 3.2.0 + twine ~= 3.3.0 commands = {envpython} -m build --outdir {distdir} . twine {posargs:check} {distdir}/* [testenv:static] deps = - bandit ~= 1.6.2 + bandit ~= 1.7.0 black ~= 20.8b flake8 ~= 3.8.3 flake8-commas ~= 2.0.0 - mypy >= 0.790, < 0.800 + mypy >= 0.810, < 0.820 commands = black --check src setup.py tests flake8 src setup.py tests