diff options
author | Ned Batchelder <ned@nedbatchelder.com> | 2023-01-03 15:21:15 -0500 |
---|---|---|
committer | Ned Batchelder <ned@nedbatchelder.com> | 2023-01-03 15:29:00 -0500 |
commit | 4f3ccf213d813bb57775b2643b8bae5e08cbbbd0 (patch) | |
tree | fb113cb24362ee3fd67825867874ce3aa713b55e | |
parent | 98301ed240a141592573c2ed239e006d42a26161 (diff) | |
download | python-coveragepy-git-4f3ccf213d813bb57775b2643b8bae5e08cbbbd0.tar.gz |
refactor: a better way to have maybe-importable third-party modules
-rw-r--r-- | coverage/misc.py | 8 | ||||
-rw-r--r-- | coverage/tomlconfig.py | 6 | ||||
-rw-r--r-- | tests/helpers.py | 20 | ||||
-rw-r--r-- | tests/test_config.py | 11 | ||||
-rw-r--r-- | tests/test_misc.py | 7 | ||||
-rw-r--r-- | tests/test_testing.py | 13 |
6 files changed, 18 insertions, 47 deletions
diff --git a/coverage/misc.py b/coverage/misc.py index 1e4b4e74..bd1767ca 100644 --- a/coverage/misc.py +++ b/coverage/misc.py @@ -83,14 +83,16 @@ def import_third_party(modname): modname (str): the name of the module to import. Returns: - The imported module, or None if the module couldn't be imported. + The imported module, and a boolean indicating if the module could be imported. + + If the boolean is False, the module returned is not the one you want: don't use it. """ with sys_modules_saved(): try: - return importlib.import_module(modname) + return importlib.import_module(modname), True except ImportError: - return None + return sys, False def nice_pair(pair): diff --git a/coverage/tomlconfig.py b/coverage/tomlconfig.py index 31cd0bb0..737c728c 100644 --- a/coverage/tomlconfig.py +++ b/coverage/tomlconfig.py @@ -5,7 +5,6 @@ import os import re -import sys from typing import Any, Callable, Dict, Iterable, List, Optional, Tuple, Type, TypeVar @@ -17,9 +16,10 @@ from coverage.types import TConfigSection, TConfigValue if env.PYVERSION >= (3, 11, 0, "alpha", 7): import tomllib # pylint: disable=import-error + has_tomllib = True else: # TOML support on Python 3.10 and below is an install-time extra option. - tomllib = import_third_party("tomli") + tomllib, has_tomllib = import_third_party("tomli") class TomlDecodeError(Exception): @@ -51,7 +51,7 @@ class TomlConfigParser: toml_text = fp.read() except OSError: return [] - if sys.version_info >= (3, 11) or tomllib is not None: + if has_tomllib: try: self.data = tomllib.loads(toml_text) except tomllib.TOMLDecodeError as err: diff --git a/tests/helpers.py b/tests/helpers.py index 1645138c..0503097e 100644 --- a/tests/helpers.py +++ b/tests/helpers.py @@ -15,12 +15,10 @@ import subprocess import textwrap import warnings -from types import ModuleType from typing import ( cast, Any, Callable, Generator, Iterable, List, Optional, Set, Tuple, Type, Union, ) -from unittest import mock import pytest @@ -284,24 +282,6 @@ def change_dir(new_dir: str) -> Generator[None, None, None]: os.chdir(old_dir) -def without_module(using_module: ModuleType, missing_module_name: str) -> mock._patch[Any]: - """ - Hide a module for testing. - - Use this in a test function to make an optional module unavailable during - the test:: - - with without_module(product.something, 'tomli'): - use_toml_somehow() - - Arguments: - using_module: a module in which to hide `missing_module_name`. - missing_module_name (str): the name of the module to hide. - - """ - return mock.patch.object(using_module, missing_module_name, None) - - def assert_count_equal(a: Iterable[Union[int, str]], b: Iterable[Union[int, str]]) -> None: """ A pytest-friendly implementation of assertCountEqual. diff --git a/tests/test_config.py b/tests/test_config.py index 66e71d23..08fdc113 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -15,7 +15,6 @@ from coverage.exceptions import ConfigError, CoverageWarning from coverage.tomlconfig import TomlConfigParser from tests.coveragetest import CoverageTest, UsingModulesMixin -from tests.helpers import without_module class ConfigTest(CoverageTest): @@ -713,7 +712,7 @@ class ConfigFileTest(UsingModulesMixin, CoverageTest): def test_no_toml_installed_no_toml(self) -> None: # Can't read a toml file that doesn't exist. - with without_module(coverage.tomlconfig, 'tomllib'): + with mock.patch.object(coverage.tomlconfig, "has_tomllib", False): msg = "Couldn't read 'cov.toml' as a config file" with pytest.raises(ConfigError, match=msg): coverage.Coverage(config_file="cov.toml") @@ -722,7 +721,7 @@ class ConfigFileTest(UsingModulesMixin, CoverageTest): def test_no_toml_installed_explicit_toml(self) -> None: # Can't specify a toml config file if toml isn't installed. self.make_file("cov.toml", "# A toml file!") - with without_module(coverage.tomlconfig, 'tomllib'): + with mock.patch.object(coverage.tomlconfig, "has_tomllib", False): msg = "Can't read 'cov.toml' without TOML support" with pytest.raises(ConfigError, match=msg): coverage.Coverage(config_file="cov.toml") @@ -735,7 +734,7 @@ class ConfigFileTest(UsingModulesMixin, CoverageTest): [tool.coverage.run] xyzzy = 17 """) - with without_module(coverage.tomlconfig, 'tomllib'): + with mock.patch.object(coverage.tomlconfig, "has_tomllib", False): msg = "Can't read 'pyproject.toml' without TOML support" with pytest.raises(ConfigError, match=msg): coverage.Coverage() @@ -748,7 +747,7 @@ class ConfigFileTest(UsingModulesMixin, CoverageTest): [tool.coverage] run.parallel = true """) - with without_module(coverage.tomlconfig, 'tomllib'): + with mock.patch.object(coverage.tomlconfig, "has_tomllib", False): msg = "Can't read 'pyproject.toml' without TOML support" with pytest.raises(ConfigError, match=msg): coverage.Coverage() @@ -761,7 +760,7 @@ class ConfigFileTest(UsingModulesMixin, CoverageTest): [tool.something] xyzzy = 17 """) - with without_module(coverage.tomlconfig, 'tomllib'): + with mock.patch.object(coverage.tomlconfig, "has_tomllib", False): cov = coverage.Coverage() # We get default settings: assert not cov.config.timid diff --git a/tests/test_misc.py b/tests/test_misc.py index 4fd3f7c7..745522b0 100644 --- a/tests/test_misc.py +++ b/tests/test_misc.py @@ -118,7 +118,8 @@ class ImportThirdPartyTest(CoverageTest): # Make sure we don't have pytest in sys.modules before we start. del sys.modules["pytest"] # Import pytest - mod = import_third_party("pytest") + mod, has = import_third_party("pytest") + assert has # Yes, it's really pytest: assert mod.__name__ == "pytest" print(dir(mod)) @@ -127,8 +128,8 @@ class ImportThirdPartyTest(CoverageTest): assert "pytest" not in sys.modules def test_failure(self): - mod = import_third_party("xyzzy") - assert mod is None + _, has = import_third_party("xyzzy") + assert not has assert "xyzzy" not in sys.modules diff --git a/tests/test_testing.py b/tests/test_testing.py index 43c682cd..d5447c45 100644 --- a/tests/test_testing.py +++ b/tests/test_testing.py @@ -12,14 +12,13 @@ import warnings import pytest import coverage -from coverage import tomlconfig from coverage.exceptions import CoverageWarning from coverage.files import actual_path from tests.coveragetest import CoverageTest from tests.helpers import ( arcs_to_arcz_repr, arcz_to_arcs, assert_count_equal, assert_coverage_warnings, - CheckUniqueFilenames, re_lines, re_lines_text, re_line, without_module, + CheckUniqueFilenames, re_lines, re_lines_text, re_line, ) @@ -356,16 +355,6 @@ def _same_python_executable(e1, e2): return False # pragma: only failure -def test_without_module(): - toml1 = tomlconfig.tomllib - with without_module(tomlconfig, 'tomllib'): - toml2 = tomlconfig.tomllib - toml3 = tomlconfig.tomllib - - assert toml1 is toml3 is not None - assert toml2 is None - - class ArczTest(CoverageTest): """Tests of arcz/arcs helpers.""" |