summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNed Batchelder <ned@nedbatchelder.com>2023-01-03 15:21:15 -0500
committerNed Batchelder <ned@nedbatchelder.com>2023-01-03 15:29:00 -0500
commit4f3ccf213d813bb57775b2643b8bae5e08cbbbd0 (patch)
treefb113cb24362ee3fd67825867874ce3aa713b55e
parent98301ed240a141592573c2ed239e006d42a26161 (diff)
downloadpython-coveragepy-git-4f3ccf213d813bb57775b2643b8bae5e08cbbbd0.tar.gz
refactor: a better way to have maybe-importable third-party modules
-rw-r--r--coverage/misc.py8
-rw-r--r--coverage/tomlconfig.py6
-rw-r--r--tests/helpers.py20
-rw-r--r--tests/test_config.py11
-rw-r--r--tests/test_misc.py7
-rw-r--r--tests/test_testing.py13
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."""