Skip to content

Commit 9ed8c27

Browse files
committed
Allow disabling plugins on a one-off
Signed-off-by: Bernát Gábor <[email protected]>
1 parent e2ff114 commit 9ed8c27

File tree

6 files changed

+64
-7
lines changed

6 files changed

+64
-7
lines changed

docs/changelog/3468.feature.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Allow disabling tox plugins via the ``TOX_DISABLED_EXTERNAL_PLUGINS`` environment variable - by :user:`gaborbernat`.

docs/plugins.rst

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,13 @@ installed. For example:
2727
2828
For more information, refer to :ref:`the user guide <auto-provisioning>`.
2929

30+
Plugins can be disabled via the ``TOX_DISABLED_EXTERNAL_PLUGINS`` environment variable. This variable can be set to a
31+
comma separated list of plugin names, e.g.:
32+
33+
```bash
34+
env TOX_DISABLED_EXTERNAL_PLUGINS=tox-uv,tox-extra tox --version
35+
```
36+
3037
Developing your own plugin
3138
--------------------------
3239

pyproject.toml

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -62,11 +62,6 @@ dependencies = [
6262
"typing-extensions>=4.12.2; python_version<'3.11'",
6363
"virtualenv>=20.31",
6464
]
65-
optional-dependencies.test = [
66-
"devpi-process>=1.0.2",
67-
"pytest>=8.3.4",
68-
"pytest-mock>=3.14",
69-
]
7065
urls.Documentation = "https://tox.wiki"
7166
urls.Homepage = "http://tox.readthedocs.org"
7267
urls."Release Notes" = "https://tox.wiki/en/latest/changelog.html"

src/tox/plugin/manager.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
from __future__ import annotations
44

5+
import os
56
from typing import TYPE_CHECKING, Any
67

78
import pluggy
@@ -49,7 +50,7 @@ def _register_plugins(self, inline: ModuleType | None) -> None:
4950

5051
if inline is not None:
5152
self.manager.register(inline)
52-
self.manager.load_setuptools_entrypoints(NAME)
53+
self._load_external_plugins()
5354
internal_plugins = (
5455
loader_api,
5556
provision,
@@ -74,6 +75,11 @@ def _register_plugins(self, inline: ModuleType | None) -> None:
7475
self.manager.register(state)
7576
self.manager.check_pending()
7677

78+
def _load_external_plugins(self) -> None:
79+
for name in os.environ.get("TOX_DISABLED_EXTERNAL_PLUGINS", "").split(","):
80+
self.manager.set_blocked(name)
81+
self.manager.load_setuptools_entrypoints(NAME)
82+
7783
def tox_add_option(self, parser: ToxParser) -> None:
7884
self.manager.hook.tox_add_option(parser=parser)
7985

src/tox/pytest.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,10 @@ def _load_inline(path: Path) -> ModuleType | None: # register only on first run
8282
@contextmanager
8383
def check_os_environ() -> Iterator[None]:
8484
old = os.environ.copy()
85-
to_clean = {k: os.environ.pop(k, None) for k in (ENV_VAR_KEY, "TOX_WORK_DIR", "PYTHONPATH", "COV_CORE_CONTEXT")}
85+
to_clean = {
86+
k: os.environ.pop(k, None)
87+
for k in (ENV_VAR_KEY, "TOX_WORK_DIR", "PYTHONPATH", "COV_CORE_CONTEXT", "TOX_DISABLED_EXTERNAL_PLUGINS")
88+
}
8689

8790
yield
8891

@@ -93,6 +96,7 @@ def check_os_environ() -> Iterator[None]:
9396
new = os.environ
9497
extra = {k: new[k] for k in set(new) - set(old)}
9598
extra.pop("PLAT", None)
99+
extra.pop("TOX_DISABLED_EXTERNAL_PLUGINS", None)
96100
miss = {k: old[k] for k in set(old) - set(new)}
97101
diff = {
98102
f"{k} = {old[k]} vs {new[k]}" for k in set(old) & set(new) if old[k] != new[k] and not k.startswith("PYTEST_")

tests/plugin/test_plugin.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
from tox.config.sets import ConfigSet, CoreConfigSet, EnvConfigSet
1414
from tox.execute import Outcome
1515
from tox.plugin import impl
16+
from tox.plugin.manager import Plugin
1617
from tox.pytest import ToxProjectCreator, register_inline_plugin
1718
from tox.session.state import State
1819
from tox.tox_env.api import ToxEnv
@@ -21,6 +22,7 @@
2122
if TYPE_CHECKING:
2223
from pathlib import Path
2324

25+
from pluggy import PluginManager
2426
from pytest_mock import MockerFixture
2527

2628

@@ -268,3 +270,45 @@ def tox_after_run_commands(tox_env: ToxEnv, exit_code: int, outcomes: list[Outco
268270
project = tox_project({"tox.ini": "[testenv]\npackage=skip"})
269271
result = project.run("r")
270272
result.assert_success()
273+
274+
275+
@pytest.mark.parametrize(
276+
("env_val", "expect_a", "expect_b"),
277+
[
278+
pytest.param("", True, True, id="none_disabled"),
279+
pytest.param("dummy_plugin_a,dummy_plugin_b", False, False, id="both_disabled"),
280+
pytest.param("dummy_plugin_a", False, True, id="only_a_disabled"),
281+
pytest.param("dummy_plugin_b", True, False, id="only_b_disabled"),
282+
],
283+
)
284+
def test_disable_external_plugins(
285+
tox_project: ToxProjectCreator,
286+
mocker: MockerFixture,
287+
env_val: str,
288+
expect_a: bool,
289+
expect_b: bool,
290+
) -> None:
291+
class DummyPluginA:
292+
@staticmethod
293+
@impl
294+
def tox_add_option(parser: ToxParser) -> None: # noqa: ARG004
295+
logging.warning("dummy plugin A called")
296+
297+
class DummyPluginB:
298+
@staticmethod
299+
@impl
300+
def tox_add_option(parser: ToxParser) -> None: # noqa: ARG004
301+
logging.warning("dummy plugin B called")
302+
303+
def fake_load_entrypoints(self: PluginManager, name: str) -> None: # noqa: ARG001
304+
self.register(DummyPluginA(), name="dummy_plugin_a")
305+
self.register(DummyPluginB(), name="dummy_plugin_b")
306+
307+
mocker.patch("pluggy.PluginManager.load_setuptools_entrypoints", fake_load_entrypoints)
308+
mocker.patch.dict(os.environ, {"TOX_DISABLED_EXTERNAL_PLUGINS": env_val})
309+
mocker.patch("tox.plugin.manager.MANAGER", Plugin())
310+
project = tox_project({"tox.ini": ""})
311+
result = project.run("--version")
312+
result.assert_success()
313+
assert ("dummy plugin A called" in result.out) == expect_a
314+
assert ("dummy plugin B called" in result.out) == expect_b

0 commit comments

Comments
 (0)