Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Prepare Loader and MemoryLoader for TOML support #3344

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/changelog/3343.misc.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Prepare ``Loader`` and ``MemoryLoader`` for TOML support - by :user:`ziima`
2 changes: 1 addition & 1 deletion src/tox/config/loader/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
3 changes: 0 additions & 3 deletions src/tox/config/loader/ini/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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})"
19 changes: 14 additions & 5 deletions src/tox/config/loader/memory.py
Original file line number Diff line number Diff line change
@@ -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

Expand All @@ -14,9 +14,16 @@


class MemoryLoader(Loader[Any]):
def __init__(self, **kwargs: Any) -> None:
super().__init__(Section(prefix="<memory>", name=str(id(self))), [])
self.raw: dict[str, Any] = {**kwargs}
def __init__(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a backwards incompatible change and cannot happen.

self,
raw: dict[str, Any],
*,
section: Section | None = None,
overrides: list[Override] | None = None,
) -> None:
section = section or Section(prefix="<memory>", 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]
Expand Down Expand Up @@ -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)
14 changes: 8 additions & 6 deletions src/tox/provision.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
6 changes: 4 additions & 2 deletions src/tox/session/cmd/devenv.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
8 changes: 5 additions & 3 deletions src/tox/session/cmd/exec_.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion src/tox/session/cmd/legacy.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
8 changes: 1 addition & 7 deletions tests/config/loader/ini/test_ini_loader.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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)
Expand Down
21 changes: 20 additions & 1 deletion tests/config/loader/test_loader.py
Original file line number Diff line number Diff line change
Expand Up @@ -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


Expand Down Expand Up @@ -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')]})"
16 changes: 6 additions & 10 deletions tests/config/loader/test_memory_loader.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand All @@ -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
2 changes: 1 addition & 1 deletion tests/config/test_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down
2 changes: 1 addition & 1 deletion tests/config/test_sets.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"]

Expand Down
4 changes: 2 additions & 2 deletions tests/plugin/test_plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down Expand Up @@ -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"]

Expand Down
Loading