diff --git a/docs/changelog/3343.misc.rst b/docs/changelog/3343.misc.rst new file mode 100644 index 000000000..1f8e3bedc --- /dev/null +++ b/docs/changelog/3343.misc.rst @@ -0,0 +1 @@ +Prepare ``Loader`` and ``MemoryLoader`` for TOML support - by :user:`ziima` diff --git a/src/tox/config/loader/api.py b/src/tox/config/loader/api.py index d0437357b..7eee7814a 100644 --- a/src/tox/config/loader/api.py +++ b/src/tox/config/loader/api.py @@ -107,7 +107,7 @@ def found_keys(self) -> set[str]: raise NotImplementedError def __repr__(self) -> str: - return f"{type(self).__name__}" + return f"{self.__class__.__name__}(section={self._section.key}, overrides={self.overrides!r})" def __contains__(self, item: str) -> bool: return item in self.found_keys() diff --git a/src/tox/config/loader/ini/__init__.py b/src/tox/config/loader/ini/__init__.py index 5f0e5eda7..514dfbd4e 100644 --- a/src/tox/config/loader/ini/__init__.py +++ b/src/tox/config/loader/ini/__init__.py @@ -95,6 +95,3 @@ def get_section(self, name: str) -> SectionProxy | None: if self._parser.has_section(name): return self._parser[name] return None - - def __repr__(self) -> str: - return f"{self.__class__.__name__}(section={self._section.key}, overrides={self.overrides!r})" diff --git a/src/tox/config/loader/memory.py b/src/tox/config/loader/memory.py index 80ad82202..fb9182d09 100644 --- a/src/tox/config/loader/memory.py +++ b/src/tox/config/loader/memory.py @@ -1,11 +1,11 @@ from __future__ import annotations from pathlib import Path -from typing import TYPE_CHECKING, Any, Iterator +from typing import TYPE_CHECKING, Any, Iterator, Sequence from tox.config.types import Command, EnvList -from .api import Loader +from .api import Loader, Override from .section import Section from .str_convert import StrConvert @@ -14,9 +14,16 @@ class MemoryLoader(Loader[Any]): - def __init__(self, **kwargs: Any) -> None: - super().__init__(Section(prefix="", name=str(id(self))), []) - self.raw: dict[str, Any] = {**kwargs} + def __init__( + self, + raw: dict[str, Any], + *, + section: Section | None = None, + overrides: list[Override] | None = None, + ) -> None: + section = section or Section(prefix="", name=str(id(self))) + super().__init__(section, overrides or []) + self.raw = raw def load_raw(self, key: Any, conf: Config | None, env_name: str | None) -> Any: # noqa: ARG002 return self.raw[key] @@ -62,4 +69,6 @@ def to_env_list(value: Any) -> EnvList: return value if isinstance(value, str): return StrConvert.to_env_list(value) + if isinstance(value, Sequence): + return EnvList(value) raise TypeError(value) diff --git a/src/tox/provision.py b/src/tox/provision.py index 9e1e2bf8c..e19bf2187 100644 --- a/src/tox/provision.py +++ b/src/tox/provision.py @@ -91,12 +91,14 @@ def add_tox_requires_min_version(reqs: list[Requirement]) -> list[Requirement]: deps = ", ".join(f"{p}{'' if v is None else f' ({v})'}" for p, v in missing) loader = MemoryLoader( # these configuration values are loaded from in-memory always (no file conf) - base=[], # disable inheritance for provision environments - package="skip", # no packaging for this please - # use our own dependency specification - deps=PythonDeps("\n".join(str(r) for r in requires), root=state.conf.core["tox_root"]), - pass_env=["*"], # do not filter environment variables, will be handled by provisioned tox - recreate=state.conf.options.recreate and not state.conf.options.no_recreate_provision, + { + "base": [], # disable inheritance for provision environments + "package": "skip", # no packaging for this please + # use our own dependency specification + "deps": PythonDeps("\n".join(str(r) for r in requires), root=state.conf.core["tox_root"]), + "pass_env": ["*"], # do not filter environment variables, will be handled by provisioned tox + "recreate": state.conf.options.recreate and not state.conf.options.no_recreate_provision, + } ) provision_tox_env: str = state.conf.core["provision_tox_env"] state.conf.memory_seed_loaders[provision_tox_env].append(loader) diff --git a/src/tox/session/cmd/devenv.py b/src/tox/session/cmd/devenv.py index c4754e546..c5e11c257 100644 --- a/src/tox/session/cmd/devenv.py +++ b/src/tox/session/cmd/devenv.py @@ -35,8 +35,10 @@ def devenv(state: State) -> int: opt.skip_pkg_install = False # always install a package in this case opt.no_test = True # do not run the test phase loader = MemoryLoader( # these configuration values are loaded from in-memory always (no file conf) - usedevelop=True, # dev environments must be of type dev - env_dir=opt.devenv_path, # move it in source + { + "usedevelop": True, # dev environments must be of type dev + "env_dir": opt.devenv_path, # move it in source + } ) state.conf.memory_seed_loaders[next(iter(opt.env))].append(loader) diff --git a/src/tox/session/cmd/exec_.py b/src/tox/session/cmd/exec_.py index 4ce3e947e..3f94b53f7 100644 --- a/src/tox/session/cmd/exec_.py +++ b/src/tox/session/cmd/exec_.py @@ -34,9 +34,11 @@ def exec_(state: State) -> int: msg = f"exactly one target environment allowed in exec mode but found {', '.join(envs)}" raise HandledError(msg) loader = MemoryLoader( # these configuration values are loaded from in-memory always (no file conf) - commands_pre=[], - commands=[], - commands_post=[], + { + "commands_pre": [], + "commands": [], + "commands_post": [], + } ) conf = state.envs[envs[0]].conf conf.loaders.insert(0, loader) diff --git a/src/tox/session/cmd/legacy.py b/src/tox/session/cmd/legacy.py index a78d8bac7..05f122e2d 100644 --- a/src/tox/session/cmd/legacy.py +++ b/src/tox/session/cmd/legacy.py @@ -129,7 +129,7 @@ def _handle_legacy_only_flags(option: Parsed, envs: EnvSelector) -> None: # noq for env in envs.iter(only_active=True, package=False): env_conf = envs[env].conf if override: - env_conf.loaders.insert(0, MemoryLoader(**override)) + env_conf.loaders.insert(0, MemoryLoader(override)) if set_env: cast(SetEnv, env_conf["set_env"]).update(set_env, override=True) if forced: diff --git a/tests/config/loader/ini/test_ini_loader.py b/tests/config/loader/ini/test_ini_loader.py index f05f85041..6b8d5d12f 100644 --- a/tests/config/loader/ini/test_ini_loader.py +++ b/tests/config/loader/ini/test_ini_loader.py @@ -4,7 +4,7 @@ import pytest -from tox.config.loader.api import ConfigLoadArgs, Override +from tox.config.loader.api import ConfigLoadArgs from tox.config.loader.ini import IniLoader from tox.config.source.ini_section import IniSection @@ -18,12 +18,6 @@ def test_ini_loader_keys(mk_ini_conf: Callable[[str], ConfigParser]) -> None: assert loader.found_keys() == {"a", "c"} -def test_ini_loader_repr(mk_ini_conf: Callable[[str], ConfigParser]) -> None: - core = IniSection(None, "tox") - loader = IniLoader(core, mk_ini_conf("\n[tox]\n\na=b\nc=d\n\n"), [Override("tox.a=1")], core_section=core) - assert repr(loader) == "IniLoader(section=tox, overrides={'a': [Override('tox.a=1')]})" - - def test_ini_loader_has_section(mk_ini_conf: Callable[[str], ConfigParser]) -> None: core = IniSection(None, "tox") loader = IniLoader(core, mk_ini_conf("[magic]\n[tox]\n\na=b\nc=d\n\n"), [], core_section=core) diff --git a/tests/config/loader/test_loader.py b/tests/config/loader/test_loader.py index ab8c7d996..5b2290268 100644 --- a/tests/config/loader/test_loader.py +++ b/tests/config/loader/test_loader.py @@ -5,9 +5,12 @@ import pytest from tox.config.cli.parse import get_options -from tox.config.loader.api import Override +from tox.config.loader.api import Loader, Override +from tox.config.loader.section import Section +from tox.config.loader.str_convert import StrConvert if TYPE_CHECKING: + from tox.config.main import Config from tox.pytest import CaptureFixture @@ -62,3 +65,19 @@ def test_override_not_equals_different_type() -> None: def test_override_repr() -> None: assert repr(Override("b.a=c")) == "Override('b.a=c')" + + +class SimpleLoader(StrConvert, Loader[str]): + """Simple loader for tests.""" + + def load_raw(self, key: str, conf: Config | None, env_name: str | None) -> str: # type: ignore[empty-body] + pass + + def found_keys(self) -> set[str]: # type: ignore[empty-body] + pass + + +def test_loader_repr() -> None: + core = Section(None, "tox") + loader = SimpleLoader(core, [Override("tox.a=1")]) + assert repr(loader) == "SimpleLoader(section=tox, overrides={'a': [Override('tox.a=1')]})" diff --git a/tests/config/loader/test_memory_loader.py b/tests/config/loader/test_memory_loader.py index 508566df1..114e92c2b 100644 --- a/tests/config/loader/test_memory_loader.py +++ b/tests/config/loader/test_memory_loader.py @@ -11,13 +11,8 @@ from tox.config.types import Command, EnvList -def test_memory_loader_repr() -> None: - loader = MemoryLoader(a=1) - assert repr(loader) == "MemoryLoader" - - def test_memory_loader_override() -> None: - loader = MemoryLoader(a=1) + loader = MemoryLoader({"a": 1}) loader.overrides["a"] = [Override("a=2")] args = ConfigLoadArgs([], "name", None) loaded = loader.load("a", of_type=int, conf=None, factory=None, args=args) @@ -48,11 +43,12 @@ def test_memory_loader_override() -> None: (os.getcwd(), Path, Path.cwd()), # noqa: PTH109 ("pip list", Command, Command(["pip", "list"])), ("a\nb", EnvList, EnvList(["a", "b"])), + (["a", "b"], EnvList, EnvList(["a", "b"])), ("1", Optional[int], 1), ], ) def test_memory_loader(value: Any, of_type: type[Any], outcome: Any) -> None: - loader = MemoryLoader(a=value, kwargs={}) + loader = MemoryLoader({"a": value, "kwargs": {}}) args = ConfigLoadArgs([], "name", None) loaded = loader.load("a", of_type=of_type, conf=None, factory=None, args=args) assert loaded == outcome @@ -72,18 +68,18 @@ def test_memory_loader(value: Any, of_type: type[Any], outcome: Any) -> None: ], ) def test_memory_loader_fails_invalid(value: Any, of_type: type[Any], exception: Exception, msg: str) -> None: - loader = MemoryLoader(a=value, kwargs={}) + loader = MemoryLoader({"a": value, "kwargs": {}}) args = ConfigLoadArgs([], "name", None) with pytest.raises(exception, match=msg): # type: ignore[call-overload] loader.load("a", of_type=of_type, conf=None, factory=None, args=args) def test_memory_found_keys() -> None: - loader = MemoryLoader(a=1, c=2) + loader = MemoryLoader({"a": 1, "c": 2}) assert loader.found_keys() == {"a", "c"} def test_memory_loader_contains() -> None: - loader = MemoryLoader(a=1) + loader = MemoryLoader({"a": 1}) assert "a" in loader assert "b" not in loader diff --git a/tests/config/test_main.py b/tests/config/test_main.py index 853ef9bb4..070702e71 100644 --- a/tests/config/test_main.py +++ b/tests/config/test_main.py @@ -63,7 +63,7 @@ def test_config_overrides(tox_ini_conf: ToxIniCreator) -> None: def test_config_override_wins_memory_loader(tox_ini_conf: ToxIniCreator) -> None: main_conf = tox_ini_conf("[testenv]", override=[Override("testenv.c=ok")]) - conf = main_conf.get_env("py", loaders=[MemoryLoader(c="something_else")]) + conf = main_conf.get_env("py", loaders=[MemoryLoader({"c": "something_else"})]) conf.add_config("c", of_type=str, default="d", desc="desc") assert conf["c"] == "ok" diff --git a/tests/config/test_sets.py b/tests/config/test_sets.py index 91aec92c1..5db2d7ee2 100644 --- a/tests/config/test_sets.py +++ b/tests/config/test_sets.py @@ -181,7 +181,7 @@ def test_do_not_allow_create_config_set(mocker: MockerFixture) -> None: def test_set_env_raises_on_non_str(mocker: MockerFixture) -> None: env_set = EnvConfigSet(mocker.create_autospec(Config), Section("a", "b"), "b") - env_set.loaders.insert(0, MemoryLoader(set_env=1)) + env_set.loaders.insert(0, MemoryLoader({"set_env": 1})) with pytest.raises(TypeError, match="1"): assert env_set["set_env"] diff --git a/tests/plugin/test_plugin.py b/tests/plugin/test_plugin.py index 32a8e9fdc..a066c65ef 100644 --- a/tests/plugin/test_plugin.py +++ b/tests/plugin/test_plugin.py @@ -131,7 +131,7 @@ def test_plugin_can_set_core_conf( ) -> None: @impl def tox_add_core_config(core_conf: CoreConfigSet, state: State) -> None: # noqa: ARG001 - core_conf.loaders.insert(0, MemoryLoader(**{dir_name: tmp_path})) + core_conf.loaders.insert(0, MemoryLoader({dir_name: tmp_path})) register_inline_plugin(mocker, tox_add_core_config) @@ -186,7 +186,7 @@ def tox_add_core_config(core_conf: CoreConfigSet, state: State) -> None: # noqa def test_plugin_injects_invalid_python_run(tox_project: ToxProjectCreator, mocker: MockerFixture) -> None: @impl def tox_add_env_config(env_conf: EnvConfigSet, state: State) -> None: # noqa: ARG001 - env_conf.loaders.insert(0, MemoryLoader(deps=[1])) + env_conf.loaders.insert(0, MemoryLoader({"deps": [1]})) with pytest.raises(TypeError, match="1"): assert env_conf["deps"]