Skip to content

Commit 4513a3d

Browse files
authored
Merge branch 'main' into users/rahuldevikar/fix3687-scoped
2 parents 6603dc3 + e84002a commit 4513a3d

16 files changed

Lines changed: 186 additions & 21 deletions

File tree

docs/changelog/3433.bugfix.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Normalize extra names when resolving dependencies so that underscores and hyphens are treated equivalently (e.g.
2+
``extras = snake_case`` now matches ``Provides-Extra: snake-case`` in wheel metadata) - by :user:`Fridayai700`.

docs/changelog/3445.bugfix.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fix ``setenv`` modifications to ``PATH`` being overwritten when tox environment paths are set up - by
2+
:user:`Fridayai700`

docs/changelog/3557.bugfix.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fix factor selection via ``TOX_FACTORS`` environment variable producing wrong results because ``append`` + ``nargs="+"``
2+
actions need nested list types for proper env var conversion - by :user:`Fridayai700`.

docs/changelog/3574.bugfix.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fix custom ``install_command`` being ignored when specified in TOML configuration (``tox.toml``/``pyproject.toml``) - by
2+
:user:`Fridayai700`.

docs/changelog/3590.bugfix.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fix env names containing dots (e.g. ``py3.11``) losing their description in TOML configuration - by :user:`Fridayai700`.

docs/changelog/3728.bugfix.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fix type checker CI failure by adding ``completion`` extras to ``type`` and ``type-min`` environments so ``ty`` can
2+
resolve the ``argcomplete`` import - by :user:`gaborbernat`.

src/tox/config/cli/parser.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,10 @@ def get_type(action: Action) -> type[Any]:
6666
of_type: type[Any] | None = getattr(action, "of_type", None)
6767
if of_type is None:
6868
if isinstance(action, argparse._AppendAction): # noqa: SLF001
69-
of_type = list[action.type] # ty: ignore[invalid-type-form] # runtime generic from argparse action type
69+
if action.nargs in {"+", "*"} or (isinstance(action.nargs, int) and action.nargs > 1):
70+
of_type = list[list[action.type]] # ty: ignore[invalid-type-form] # nargs produces list per invocation
71+
else:
72+
of_type = list[action.type] # ty: ignore[invalid-type-form] # runtime generic from argparse action type
7073
elif isinstance(action, argparse._StoreAction) and action.choices: # noqa: SLF001
7174
loc = locals()
7275
loc["Literal"] = Literal

src/tox/config/loader/toml/_replace.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,7 @@ def __call__(self, value: str, conf_args: ConfigLoadArgs) -> str | None:
123123
default = settings["default"]
124124
if default is not None:
125125
return default
126+
return None # keep original text, consistent with ini loader behavior
126127
raise exception
127128
return value
128129

src/tox/config/source/toml_pyproject.py

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -55,11 +55,16 @@ def run_env_base(cls) -> str:
5555

5656
@property
5757
def keys(self) -> Iterable[str]:
58-
key = self.key
59-
keys = key.split(self.SEP) if self.key else []
60-
if self.PREFIX and len(keys) >= len(self.PREFIX) and tuple(keys[: len(self.PREFIX)]) == self.PREFIX:
61-
keys = keys[len(self.PREFIX) :]
62-
return keys
58+
# Build keys from prefix + name directly, preserving dots in names (e.g. env "py3.11").
59+
prefix, name = self._prefix, self._name
60+
if prefix is None and not name:
61+
return []
62+
parts: list[str] = prefix.split(self.SEP) if prefix else []
63+
if self.PREFIX and len(parts) >= len(self.PREFIX) and tuple(parts[: len(self.PREFIX)]) == self.PREFIX:
64+
parts = parts[len(self.PREFIX) :] # strip global PREFIX (e.g. ("tool", "tox"))
65+
if name:
66+
parts.append(name)
67+
return parts
6368

6469

6570
class TomlPyProjectSection(TomlSection):
@@ -113,17 +118,20 @@ def get_loader(self, section: Section, override_map: OverrideMap) -> Loader[Any]
113118

114119
def envs(self, core_conf: CoreConfigSet) -> Iterator[str]:
115120
yield from core_conf["env_list"]
116-
yield from [i.key for i in self.sections()]
121+
yield from [section.name for section in self.sections()]
117122

118123
def sections(self) -> Iterator[Section]:
119124
for env_name in self._our_content.get(self._Section.ENV, {}):
120125
if not isinstance(env_name, str):
121126
msg = f"Environment key must be string, got {env_name!r}"
122127
raise HandledError(msg)
123-
yield self._Section.from_key(env_name)
128+
yield self._Section.test_env(env_name)
124129

125130
def get_base_sections(self, base: list[str], in_section: Section) -> Iterator[Section]: # noqa: ARG002
126-
yield from [self._Section.from_key(b) for b in base]
131+
core_prefix = self._Section.core_prefix()
132+
strip = f"{core_prefix}{self._Section.SEP}" if core_prefix else ""
133+
for entry in base:
134+
yield self._Section(prefix=core_prefix or None, name=entry.removeprefix(strip))
127135

128136
def get_tox_env_section(self, item: str) -> tuple[Section, list[str], list[str]]:
129137
return self._Section.test_env(item), [self._Section.run_env_base()], [self._Section.package_env_base()]

src/tox/tox_env/api.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -386,6 +386,9 @@ def environment_variables(self) -> dict[str, str]:
386386
result["PATH"] = self._make_path()
387387
for key in set_env:
388388
result[key] = set_env.load(key)
389+
# if set_env modified PATH, re-prepend virtual-env paths (deduped) so they always come first
390+
if self._paths and "PATH" in set_env:
391+
result["PATH"] = self._make_path(result["PATH"])
389392
result["TOX_ENV_NAME"] = self.name
390393
result["TOX_WORK_DIR"] = str(self.core["work_dir"])
391394
result["TOX_ENV_DIR"] = str(self.conf["env_dir"])
@@ -406,21 +409,18 @@ def _paths(self) -> list[Path]:
406409
@_paths.setter
407410
def _paths(self, value: list[Path]) -> None:
408411
self._paths_private = value
409-
# also update the environment variable with the new value
410-
if self._env_vars is not None: # pragma: no branch
411-
# remove duplicates and prepend the tox env paths
412-
result = self._make_path()
413-
self._env_vars["PATH"] = result
412+
# Invalidate cached env vars so they rebuild on next access, preserving set_env PATH modifications.
413+
self._env_vars = None
414414

415415
@property
416416
def _allow_externals(self) -> list[str]:
417417
result: list[str] = [f"{i}{os.sep}*" for i in self._paths]
418418
result.extend(i.strip() for i in self.conf["allowlist_externals"])
419419
return result
420420

421-
def _make_path(self) -> str:
421+
def _make_path(self, existing: str | None = None) -> str:
422422
values = dict.fromkeys(str(i) for i in self._paths)
423-
values.update(dict.fromkeys(os.environ.get("PATH", "").split(os.pathsep)))
423+
values.update(dict.fromkeys((existing or os.environ.get("PATH", "")).split(os.pathsep)))
424424
return os.pathsep.join(values)
425425

426426
def execute( # noqa: PLR0913

0 commit comments

Comments
 (0)