Skip to content

Commit 2d29a43

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

File tree

6 files changed

+78
-7
lines changed

6 files changed

+78
-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: 58 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,59 @@ 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+
def test_disable_external_plugins(tox_project: ToxProjectCreator, mocker: MockerFixture) -> None:
276+
class DummyPluginA:
277+
@staticmethod
278+
@impl
279+
def tox_add_option(parser: ToxParser) -> None: # noqa: ARG004
280+
logging.warning("dummy plugin A called")
281+
282+
class DummyPluginB:
283+
@staticmethod
284+
@impl
285+
def tox_add_option(parser: ToxParser) -> None: # noqa: ARG004
286+
logging.warning("dummy plugin B called")
287+
288+
def fake_load_entrypoints(self: PluginManager, name: str) -> None: # noqa: ARG001
289+
self.register(DummyPluginA(), name="dummy_plugin_a")
290+
self.register(DummyPluginB(), name="dummy_plugin_b")
291+
292+
mocker.patch("pluggy.PluginManager.load_setuptools_entrypoints", fake_load_entrypoints)
293+
294+
mocker.patch.dict(os.environ, {"TOX_DISABLED_EXTERNAL_PLUGINS": ""})
295+
mocker.patch("tox.plugin.manager.MANAGER", Plugin())
296+
297+
project = tox_project({"tox.ini": ""})
298+
result = project.run("--version")
299+
result.assert_success()
300+
assert "dummy plugin A called" in result.out
301+
assert "dummy plugin B called" in result.out
302+
303+
mocker.patch.dict(os.environ, {"TOX_DISABLED_EXTERNAL_PLUGINS": "dummy_plugin_a,dummy_plugin_b"})
304+
mocker.patch("tox.plugin.manager.MANAGER", Plugin())
305+
306+
project = tox_project({"tox.ini": ""})
307+
result = project.run("--version")
308+
result.assert_success()
309+
assert "dummy plugin A called" not in result.out
310+
assert "dummy plugin B called" not in result.out
311+
312+
mocker.patch.dict(os.environ, {"TOX_DISABLED_EXTERNAL_PLUGINS": "dummy_plugin_a"})
313+
mocker.patch("tox.plugin.manager.MANAGER", Plugin())
314+
315+
project = tox_project({"tox.ini": ""})
316+
result = project.run("--version")
317+
result.assert_success()
318+
assert "dummy plugin A called" not in result.out
319+
assert "dummy plugin B called" in result.out
320+
321+
mocker.patch.dict(os.environ, {"TOX_DISABLED_EXTERNAL_PLUGINS": "dummy_plugin_b"})
322+
mocker.patch("tox.plugin.manager.MANAGER", Plugin())
323+
324+
project = tox_project({"tox.ini": ""})
325+
result = project.run("--version")
326+
result.assert_success()
327+
assert "dummy plugin A called" in result.out
328+
assert "dummy plugin B called" not in result.out

0 commit comments

Comments
 (0)