1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
|
"""Load """
from __future__ import annotations
from collections import defaultdict
from configparser import ConfigParser
from itertools import chain
from pathlib import Path
from typing import DefaultDict, Iterable, Iterator
from tox.config.loader.ini.factor import find_envs
from ..loader.api import OverrideMap
from ..loader.ini import IniLoader
from ..loader.section import Section
from ..sets import ConfigSet
from .api import Source
from .ini_section import CORE, PKG_ENV_PREFIX, TEST_ENV_PREFIX, IniSection
class IniSource(Source):
"""Configuration sourced from a ini file (such as tox.ini)"""
CORE_SECTION = CORE
def __init__(self, path: Path, content: str | None = None) -> None:
super().__init__(path)
self._parser = ConfigParser(interpolation=None)
if content is None:
if not path.exists():
raise ValueError
content = path.read_text()
self._parser.read_string(content, str(path))
self._section_mapping: DefaultDict[str, list[str]] = defaultdict(list)
def transform_section(self, section: Section) -> Section:
return IniSection(section.prefix, section.name)
def sections(self) -> Iterator[IniSection]:
for section in self._parser.sections():
yield IniSection.from_key(section)
def get_loader(self, section: Section, override_map: OverrideMap) -> IniLoader | None:
# look up requested section name in the generative testenv mapping to find the real config source
for key in self._section_mapping.get(section.name) or []:
if section.prefix is None or Section.from_key(key).prefix == section.prefix:
break
else:
# if no matching section/prefix is found, use the requested section key as-is (for custom prefixes)
key = section.key
if self._parser.has_section(key):
return IniLoader(
section=section,
parser=self._parser,
overrides=override_map.get(section.key, []),
core_section=self.CORE_SECTION,
section_key=key,
)
return None
def get_core_section(self) -> Section:
return self.CORE_SECTION
def get_base_sections(self, base: list[str], in_section: Section) -> Iterator[Section]:
for a_base in base:
section = IniSection.from_key(a_base)
yield section # the base specifier is explicit
if in_section.prefix is not None: # no prefix specified, so this could imply our own prefix
yield IniSection(in_section.prefix, a_base)
def get_tox_env_section(self, item: str) -> tuple[Section, list[str], list[str]]:
return IniSection.test_env(item), [TEST_ENV_PREFIX], [PKG_ENV_PREFIX]
def envs(self, core_config: ConfigSet) -> Iterator[str]:
seen = set()
for name in self._discover_tox_envs(core_config):
if name not in seen:
seen.add(name)
yield name
def _discover_tox_envs(self, core_config: ConfigSet) -> Iterator[str]:
def register_factors(envs: Iterable[str]) -> None:
known_factors.update(chain.from_iterable(e.split("-") for e in envs))
explicit = list(core_config["env_list"])
yield from explicit
known_factors: set[str] = set()
register_factors(explicit)
# discover all additional defined environments, including generative section headers
for section in self.sections():
if section.is_test_env:
register_factors(section.names)
for name in section.names:
self._section_mapping[name].append(section.key)
yield name
# add all conditional markers that are not part of the explicitly defined sections
for section in self.sections():
yield from self._discover_from_section(section, known_factors)
def _discover_from_section(self, section: IniSection, known_factors: set[str]) -> Iterator[str]:
for value in self._parser[section.key].values():
for env in find_envs(value):
if set(env.split("-")) - known_factors:
yield env
def __repr__(self) -> str:
return f"{type(self).__name__}(path={self.path})"
__all__ = [
"IniSource",
]
|