Skip to content

Commit 8cdcc06

Browse files
authored
Internal changes to support tox-gh-actions (#2191)
1 parent eac98db commit 8cdcc06

File tree

15 files changed

+149
-71
lines changed

15 files changed

+149
-71
lines changed

docs/changelog/2191.bugfix.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fix the ``tox_configure`` plugin entrypoint is not called -- by :user:`gaborbernat`.

docs/changelog/2191.feature.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Expose the parsed CLI arguments on the main configuration object for plugins and allow plugins to define their own
2+
configuration section -- by :user:`gaborbernat`.

src/tox/config/loader/api.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,10 +49,15 @@ def __ne__(self, other: Any) -> bool:
4949
class Loader(Convert[T]):
5050
"""Loader loads a configuration value and converts it."""
5151

52-
def __init__(self, overrides: List[Override]) -> None:
52+
def __init__(self, section: str, overrides: List[Override]) -> None:
53+
self._section_name = section
5354
self.overrides = {o.key: o for o in overrides}
5455
self.parent: Optional["Loader[Any]"] = None
5556

57+
@property
58+
def section_name(self) -> str:
59+
return self._section_name
60+
5661
@abstractmethod
5762
def load_raw(self, key: str, conf: Optional["Config"], env_name: Optional[str]) -> T: # noqa: U100
5863
"""

src/tox/config/loader/ini/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ def __init__(self, section: str, parser: ConfigParser, overrides: List[Override]
2626
self._section: SectionProxy = parser[section]
2727
self._parser = parser
2828
self.core_prefix = core_prefix
29-
super().__init__(overrides)
29+
super().__init__(section, overrides)
3030

3131
def load_raw(self, key: str, conf: Optional["Config"], env_name: Optional[str]) -> str:
3232
return self.process_raw(conf, env_name, self._section[key])

src/tox/config/loader/memory.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010

1111
class MemoryLoader(Loader[Any]):
1212
def __init__(self, **kwargs: Any) -> None:
13-
super().__init__([])
13+
super().__init__("<memory>", [])
1414
self.raw: Dict[str, Any] = {**kwargs}
1515

1616
def load_raw(self, key: Any, conf: Optional["Config"], env_name: Optional[str]) -> T: # noqa: U100

src/tox/config/main.py

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,48 @@
11
import os
22
from collections import OrderedDict, defaultdict
33
from pathlib import Path
4-
from typing import TYPE_CHECKING, Any, Callable, Dict, Iterator, List, Optional, Sequence, Tuple
4+
from typing import TYPE_CHECKING, Any, Callable, Dict, Iterator, Optional, Sequence, Tuple, Type, TypeVar
55

6-
from tox.config.loader.api import Loader, Override, OverrideMap
6+
from tox.config.loader.api import Loader, OverrideMap
77

8-
from .sets import CoreConfigSet, EnvConfigSet
8+
from .sets import ConfigSet, CoreConfigSet, EnvConfigSet
99
from .source import Source
1010

1111
if TYPE_CHECKING:
1212
from .cli.parser import Parsed
1313

14+
T = TypeVar("T", bound=ConfigSet)
15+
1416

1517
class Config:
1618
"""Main configuration object for tox."""
1719

1820
def __init__(
1921
self,
2022
config_source: Source,
21-
overrides: List[Override],
23+
options: "Parsed",
2224
root: Path,
2325
pos_args: Optional[Sequence[str]],
2426
work_dir: Path,
2527
) -> None:
2628
self._pos_args = None if pos_args is None else tuple(pos_args)
2729
self._work_dir = work_dir
2830
self._root = root
31+
self._options = options
2932

3033
self._overrides: OverrideMap = defaultdict(list)
31-
for override in overrides:
34+
for override in options.override:
3235
self._overrides[override.namespace].append(override)
3336

3437
self._src = config_source
3538
self._env_to_set: Dict[str, EnvConfigSet] = OrderedDict()
3639
self._core_set: Optional[CoreConfigSet] = None
3740
self.register_config_set: Callable[[str, EnvConfigSet], Any] = lambda n, e: None
3841

42+
from tox.plugin.manager import MANAGER
43+
44+
MANAGER.tox_configure(self)
45+
3946
def pos_args(self, to_path: Optional[Path]) -> Optional[Tuple[str, ...]]:
4047
"""
4148
:param to_path: if not None rewrite relative posargs paths from cwd to to_path
@@ -85,18 +92,22 @@ def make(cls, parsed: "Parsed", pos_args: Optional[Sequence[str]], source: Sourc
8592
work_dir: Path = source.path.parent if parsed.work_dir is None else parsed.work_dir
8693
return cls(
8794
config_source=source,
88-
overrides=parsed.override,
95+
options=parsed,
8996
pos_args=pos_args,
9097
root=root,
9198
work_dir=work_dir,
9299
)
93100

101+
@property
102+
def options(self) -> "Parsed":
103+
return self._options
104+
94105
@property
95106
def core(self) -> CoreConfigSet:
96107
""":return: the core configuration"""
97108
if self._core_set is not None:
98109
return self._core_set
99-
core = CoreConfigSet(self, self._root)
110+
core = CoreConfigSet(self, self._root, self.src_path)
100111
for loader in self._src.get_core(self._overrides):
101112
core.loaders.append(loader)
102113

@@ -106,6 +117,12 @@ def core(self) -> CoreConfigSet:
106117
self._core_set = core
107118
return core
108119

120+
def get_section_config(self, section_name: str, of_type: Type[T]) -> T:
121+
conf_set = of_type(self)
122+
for loader in self._src.get_section(section_name, self._overrides):
123+
conf_set.loaders.append(loader)
124+
return conf_set
125+
109126
def get_env(
110127
self, item: str, package: bool = False, loaders: Optional[Sequence[Loader[Any]]] = None
111128
) -> EnvConfigSet:

src/tox/config/of_type.py

Lines changed: 25 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -18,19 +18,22 @@
1818
class ConfigDefinition(ABC, Generic[T]):
1919
"""Abstract base class for configuration definitions"""
2020

21-
def __init__(self, keys: Iterable[str], desc: str, env_name: Optional[str]) -> None:
21+
def __init__(self, keys: Iterable[str], desc: str) -> None:
2222
self.keys = keys
2323
self.desc = desc
24-
self.env_name = env_name
2524

2625
@abstractmethod
2726
def __call__(
28-
self, conf: "Config", key: Optional[str], loaders: List[Loader[T]], chain: List[str] # noqa: U100
27+
self,
28+
conf: "Config", # noqa: U100
29+
loaders: List[Loader[T]], # noqa: U100
30+
env_name: Optional[str], # noqa: U100
31+
chain: Optional[List[str]], # noqa: U100
2932
) -> T:
3033
raise NotImplementedError
3134

3235
def __eq__(self, o: Any) -> bool:
33-
return type(self) == type(o) and (self.keys, self.desc, self.env_name) == (o.keys, o.desc, o.env_name)
36+
return type(self) == type(o) and (self.keys, self.desc) == (o.keys, o.desc)
3437

3538
def __ne__(self, o: Any) -> bool:
3639
return not (self == o)
@@ -43,14 +46,17 @@ def __init__(
4346
self,
4447
keys: Iterable[str],
4548
desc: str,
46-
env_name: Optional[str],
4749
value: Union[Callable[[], T], T],
4850
) -> None:
49-
super().__init__(keys, desc, env_name)
51+
super().__init__(keys, desc)
5052
self.value = value
5153

5254
def __call__(
53-
self, conf: "Config", name: Optional[str], loaders: List[Loader[T]], chain: List[str] # noqa: U100
55+
self,
56+
conf: "Config", # noqa: U100
57+
loaders: List[Loader[T]], # noqa: U100
58+
env_name: Optional[str], # noqa: U100
59+
chain: Optional[List[str]], # noqa: U100
5460
) -> T:
5561
if callable(self.value):
5662
value = self.value()
@@ -72,36 +78,39 @@ def __init__(
7278
self,
7379
keys: Iterable[str],
7480
desc: str,
75-
env_name: Optional[str],
7681
of_type: Type[T],
7782
default: Union[Callable[["Config", Optional[str]], T], T],
7883
post_process: Optional[Callable[[T], T]] = None,
7984
kwargs: Optional[Mapping[str, Any]] = None,
8085
) -> None:
81-
super().__init__(keys, desc, env_name)
86+
super().__init__(keys, desc)
8287
self.of_type = of_type
8388
self.default = default
8489
self.post_process = post_process
8590
self.kwargs: Mapping[str, Any] = {} if kwargs is None else kwargs
8691
self._cache: Union[object, T] = _PLACE_HOLDER
8792

8893
def __call__(
89-
self,
90-
conf: "Config",
91-
name: Optional[str], # noqa: U100
92-
loaders: List[Loader[T]],
93-
chain: List[str],
94+
self, conf: "Config", loaders: List[Loader[T]], env_name: Optional[str], chain: Optional[List[str]]
9495
) -> T:
96+
if chain is None:
97+
chain = []
9598
if self._cache is _PLACE_HOLDER:
9699
for key, loader in product(self.keys, loaders):
100+
chain_key = f"{loader.section_name}.{key}"
101+
if chain_key in chain:
102+
raise ValueError(f"circular chain detected {', '.join(chain[chain.index(chain_key):])}")
103+
chain.append(chain_key)
97104
try:
98-
value = loader.load(key, self.of_type, self.kwargs, conf, self.env_name, chain)
105+
value = loader.load(key, self.of_type, self.kwargs, conf, env_name, chain)
99106
except KeyError:
100107
continue
101108
else:
102109
break
110+
finally:
111+
del chain[-1]
103112
else:
104-
value = self.default(conf, self.env_name) if callable(self.default) else self.default
113+
value = self.default(conf, env_name) if callable(self.default) else self.default
105114
if self.post_process is not None:
106115
value = self.post_process(value) # noqa
107116
self._cache = value

src/tox/config/sets.py

Lines changed: 28 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,7 @@
3030
class ConfigSet:
3131
"""A set of configuration that belong together (such as a tox environment settings, core tox settings)"""
3232

33-
def __init__(self, conf: "Config", name: Optional[str]):
34-
self._name = name
33+
def __init__(self, conf: "Config"):
3534
self._conf = conf
3635
self.loaders: List[Loader[Any]] = []
3736
self._defined: Dict[str, ConfigDefinition[Any]] = {}
@@ -59,7 +58,7 @@ def add_config(
5958
:return: the new dynamic config definition
6059
"""
6160
keys_ = self._make_keys(keys)
62-
definition = ConfigDynamicDefinition(keys_, desc, self._name, of_type, default, post_process, kwargs)
61+
definition = ConfigDynamicDefinition(keys_, desc, of_type, default, post_process, kwargs)
6362
result = self._add_conf(keys_, definition)
6463
return cast(ConfigDynamicDefinition[V], result)
6564

@@ -73,7 +72,7 @@ def add_constant(self, keys: Union[str, Sequence[str]], desc: str, value: V) ->
7372
:return: the new constant config value
7473
"""
7574
keys_ = self._make_keys(keys)
76-
definition = ConfigConstantDefinition(keys_, desc, self._name, value)
75+
definition = ConfigConstantDefinition(keys_, desc, value)
7776
result = self._add_conf(keys_, definition)
7877
return cast(ConfigConstantDefinition[V], result)
7978

@@ -84,12 +83,7 @@ def _make_keys(keys: Union[str, Sequence[str]]) -> Sequence[str]:
8483
def _add_conf(self, keys: Sequence[str], definition: ConfigDefinition[V]) -> ConfigDefinition[V]:
8584
key = keys[0]
8685
if key in self._defined:
87-
earlier = self._defined[key]
88-
# core definitions may be defined multiple times as long as all their options match, first defined wins
89-
if self._name is None and definition == earlier:
90-
definition = earlier
91-
else:
92-
raise ValueError(f"config {key} already defined")
86+
self._on_duplicate_conf(key, definition)
9387
else:
9488
self._keys[key] = None
9589
for item in keys:
@@ -98,6 +92,11 @@ def _add_conf(self, keys: Sequence[str], definition: ConfigDefinition[V]) -> Con
9892
self._defined[key] = definition
9993
return definition
10094

95+
def _on_duplicate_conf(self, key: str, definition: ConfigDefinition[V]) -> None:
96+
earlier = self._defined[key]
97+
if definition != earlier: # pragma: no branch
98+
raise ValueError(f"config {key} already defined")
99+
101100
def __getitem__(self, item: str) -> Any:
102101
"""
103102
Get the config value for a given key (will materialize in case of dynamic config).
@@ -116,18 +115,14 @@ def load(self, item: str, chain: Optional[List[str]] = None) -> Any:
116115
:return: the configuration value
117116
"""
118117
config_definition = self._defined[item]
119-
if chain is None:
120-
chain = []
121-
env_name = "tox" if self._name is None else f"testenv:{self._name}"
122-
key = f"{env_name}.{item}"
123-
if key in chain:
124-
raise ValueError(f"circular chain detected {', '.join(chain[chain.index(key):])}")
125-
chain.append(key)
126-
return config_definition(self._conf, item, self.loaders, chain)
118+
return config_definition.__call__(self._conf, self.loaders, self.name, chain)
119+
120+
@property
121+
def name(self) -> Optional[str]:
122+
return None
127123

128124
def __repr__(self) -> str:
129-
values = (v for v in (f"name={self._name!r}" if self._name else "", f"loaders={self.loaders!r}") if v)
130-
return f"{self.__class__.__name__}({', '.join(values)})"
125+
return f"{self.__class__.__name__}(loaders={self.loaders!r})"
131126

132127
def __iter__(self) -> Iterator[str]:
133128
""":return: iterate through the defined config keys (primary keys used)"""
@@ -166,8 +161,9 @@ def primary_key(self, key: str) -> str:
166161
class CoreConfigSet(ConfigSet):
167162
"""Configuration set for the core tox config"""
168163

169-
def __init__(self, conf: "Config", root: Path) -> None:
170-
super().__init__(conf, name=None)
164+
def __init__(self, conf: "Config", root: Path, src_path: Path) -> None:
165+
super().__init__(conf)
166+
self.add_constant(keys=["config_file_path"], desc="path to the configuration file", value=src_path)
171167
self.add_config(
172168
keys=["tox_root", "toxinidir"],
173169
of_type=Path,
@@ -198,12 +194,16 @@ def work_dir_builder(conf: "Config", env_name: Optional[str]) -> Path: # noqa
198194
desc="define environments to automatically run",
199195
)
200196

197+
def _on_duplicate_conf(self, key: str, definition: ConfigDefinition[V]) -> None: # noqa: U100
198+
pass # core definitions may be defined multiple times as long as all their options match, first defined wins
199+
201200

202201
class EnvConfigSet(ConfigSet):
203202
"""Configuration set for a tox environment"""
204203

205-
def __init__(self, conf: "Config", name: Optional[str]):
206-
super().__init__(conf, name=name)
204+
def __init__(self, conf: "Config", name: str):
205+
self._name = name
206+
super().__init__(conf)
207207
self.default_set_env_loader: Callable[[], Mapping[str, str]] = lambda: {}
208208

209209
def set_env_post_process(values: SetEnv) -> SetEnv:
@@ -220,7 +220,10 @@ def set_env_post_process(values: SetEnv) -> SetEnv:
220220

221221
@property
222222
def name(self) -> str:
223-
return self._name # type: ignore
223+
return self._name
224+
225+
def __repr__(self) -> str:
226+
return f"{self.__class__.__name__}(name={self._name!r}, loaders={self.loaders!r})"
224227

225228

226229
__all__ = (

src/tox/config/source/api.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,17 @@ def get_core(self, override_map: OverrideMap) -> Iterator[Loader[Any]]: # noqa:
2828
"""
2929
raise NotImplementedError
3030

31+
@abstractmethod
32+
def get_section(self, name: str, override_map: OverrideMap) -> Iterator[Loader[Any]]: # noqa: U100
33+
"""
34+
Return a loader that loads the core configuration values.
35+
36+
:param name: name of the section to load
37+
:param override_map: a list of overrides to apply
38+
:returns: the core loader from this source
39+
"""
40+
raise NotImplementedError
41+
3142
@abstractmethod
3243
def get_env_loaders(
3344
self, env_name: str, override_map: OverrideMap, package: bool, conf: ConfigSet # noqa: U100

0 commit comments

Comments
 (0)