Skip to content

Commit 34d3adc

Browse files
authored
Fix TOML configuration errors (#3388)
1 parent 719b346 commit 34d3adc

26 files changed

+276
-189
lines changed

.pre-commit-config.yaml

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,6 @@ repos:
1414
hooks:
1515
- id: codespell
1616
additional_dependencies: ["tomli>=2.0.1"]
17-
- repo: https://github.com/tox-dev/tox-ini-fmt
18-
rev: "1.4.1"
19-
hooks:
20-
- id: tox-ini-fmt
21-
args: ["-p", "fix"]
2217
- repo: https://github.com/tox-dev/pyproject-fmt
2318
rev: "2.2.4"
2419
hooks:

docs/changelog/3386.bugfix.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fix error when using ``requires`` within a TOML configuration file - by :user:`gaborbernat`.

docs/changelog/3387.bugfix.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fix error when using ``deps`` within a TOML configuration file - by :user:`gaborbernat`.

docs/changelog/3388.bugfix.rst

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
Multiple fixes for the TOML configuration by :user:`gaborbernat`.:
2+
3+
- Do not fail when there is an empty command within ``commands``.
4+
- Allow references for ``set_env`` by accepting list of dictionaries for it.
5+
- Do not try to be smart about reference unrolling, instead allow the user to control it via the ``extend`` flag,
6+
available both for ``posargs`` and ``ref`` replacements.
7+
- The ``ref`` replacements ``raw`` key has been renamed to ``of``.

docs/config.rst

Lines changed: 72 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1296,6 +1296,39 @@ others to avoid repeating the same values:
12961296
If the target table is one of the tox environments variable substitution will be applied on the replaced value,
12971297
otherwise the text will be inserted as is (e.g., here with extra).
12981298

1299+
Configuration reference
1300+
~~~~~~~~~~~~~~~~~~~~~~~
1301+
.. versionadded:: 4.21
1302+
1303+
You can reference other configurations via the ``ref`` replacement. This can either be of type:
1304+
1305+
1306+
- ``env``, in this case the configuration is loaded from another tox environment, where string substitution will happen
1307+
in that environments scope:
1308+
1309+
.. code-block:: toml
1310+
1311+
[env.src]
1312+
extras = ["A", "{env_name}"]
1313+
[env.dest]
1314+
extras = [{ replace = "ref", env = "src", key = "extras", extend = true }, "B"
1315+
1316+
In this case ``dest`` environments ``extras`` will be ``A``, ``src``, ``B``.
1317+
1318+
- ``raw``, in this case the configuration is loaded as raw, and substitution executed in the current environments scope:
1319+
1320+
.. code-block:: toml
1321+
1322+
[env.src]
1323+
extras = ["A", "{env_name}"]
1324+
[env.dest]
1325+
extras = [{ replace = "ref", of = ["env", "extras"], extend = true }, "B"]
1326+
1327+
In this case ``dest`` environments ``extras`` will be ``A``, ``dest``, ``B``.
1328+
1329+
The ``extend`` flag controls if after replacement the value should be replaced as is in the host structure (when flag is
1330+
false -- by default) or be extended into. This flag only operates when the host is a list.
1331+
12991332
Positional argument reference
13001333
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
13011334
.. versionadded:: 4.21
@@ -1305,9 +1338,12 @@ You can reference positional arguments via the ``posargs`` replacement:
13051338
.. code-block:: toml
13061339
13071340
[env.A]
1308-
commands = [["python", { replace = "posargs", default = ["a", "b"] } ]]
1341+
commands = [["python", { replace = "posargs", default = ["a", "b"], extend = true } ]]
13091342
13101343
If the positional arguments are not set commands will become ``python a b``, otherwise will be ``python posarg-set``.
1344+
The ``extend`` option instructs tox to unroll the positional arguments within the host structure. Without it the result
1345+
would become ``["python", ["a", "b"]`` which would be invalid.
1346+
13111347
Note that:
13121348

13131349
.. code-block:: toml
@@ -1318,48 +1354,60 @@ Note that:
13181354
Differs in sense that the positional arguments will be set as a single argument, while in the original example they are
13191355
passed through as separate.
13201356

1321-
Environment variable reference
1322-
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1323-
.. versionadded:: 4.21
1324-
1325-
You can reference environment variables via the ``env`` replacement:
1357+
Empty commands groups will be ignored:
13261358

13271359
.. code-block:: toml
13281360
13291361
[env.A]
1330-
set_env.COVERAGE_FILE = { replace = "env", name = "COVERAGE_FILE", default = "ok" }
1362+
commands = [[], ["pytest]]
13311363
1332-
If the environment variable is set the the ``COVERAGE_FILE`` will become that, otherwise will default to ``ok``.
1364+
will only invoke pytest. This is especially useful together with posargs allowing you to opt out of running a set of
1365+
commands:
13331366

1334-
Other configuration reference
1335-
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1367+
.. code-block:: toml
1368+
1369+
[env.A]
1370+
commands = [
1371+
{ replace = "posargs", default = ["python", "patch.py"]},
1372+
["pytest"]
1373+
]
1374+
1375+
When running ``tox run -e A`` it will invoke ``python patch.py`` followed by pytest. When running ``tox run -e A --`` it
1376+
will invoke only pytest.
1377+
1378+
1379+
Environment variable reference
1380+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
13361381
.. versionadded:: 4.21
13371382

13381383
You can reference environment variables via the ``env`` replacement:
13391384

13401385
.. code-block:: toml
13411386
1342-
[env_run_base]
1343-
extras = ["A", "{env_name}"]
1344-
[env.ab]
1345-
extras = [{ replace = "ref", raw = ["env_run_base", "extras"] }, "B"]
1387+
[env.A]
1388+
set_env.COVERAGE_FILE = { replace = "env", name = "COVERAGE_FILE", default = "ok" }
13461389
1347-
In this case the ``extras`` for ``ab`` will be ``A``, ``B`` and ``ab``.
1390+
If the environment variable is set the the ``COVERAGE_FILE`` will become that, otherwise will default to ``ok``.
13481391

1349-
Reference replacement rules
1350-
~~~~~~~~~~~~~~~~~~~~~~~~~~~
1392+
References within set_env
1393+
~~~~~~~~~~~~~~~~~~~~~~~~~
1394+
.. versionadded:: 4.21.1
13511395

1352-
When the replacement happens within a list and the returned value is also of type list the content will be extending the
1353-
list rather than replacing it. For example:
1396+
When you want to inherit ``set_env`` from another environment you can use the feature that if you pass a list of
1397+
dictionaries to ``set_env`` they will be merged together, for example:
13541398

13551399
.. code-block:: toml
13561400
1357-
[env_run_base]
1358-
extras = ["A"]
1359-
[env.ab]
1360-
extras = [{ replace = "ref", raw = ["env_run_base", "extras"] }, "B"]
1401+
[tool.tox.env_run_base]
1402+
set_env = { A = "1", B = "2"}
1403+
1404+
[tool.tox.env.magic]
1405+
set_env = [
1406+
{ replace = "ref", of = ["tool", "tox", "env_run_base", "set_env"]},
1407+
{ C = "3", D = "4"},
1408+
]
13611409
1362-
In this case the ``extras`` will be ``'A', 'B'`` rather than ``['A'], 'B'``. Otherwise the replacement is in-place.
1410+
Here the ``magic`` tox environment will have both ``A``, ``B``, ``C`` and ``D`` environments set.
13631411

13641412
INI only
13651413
--------

docs/user_guide.rst

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -33,18 +33,18 @@ these. The canonical file for this is either a ``tox.toml`` or ``tox.ini`` file.
3333
"pytest>=8",
3434
"pytest-sugar"
3535
]
36-
commands = [["pytest", { replace = "posargs", default = ["tests"] }]]
36+
commands = [["pytest", { replace = "posargs", default = ["tests"], extend = true }]]
3737
3838
[env.lint]
3939
description = "run linters"
4040
skip_install = true
4141
deps = ["black"]
42-
commands = [["black", { replace = "posargs", default = ["."]} ]]
42+
commands = [["black", { replace = "posargs", default = ["."], extend = true} ]]
4343
4444
[env.type]
4545
description = "run type checks"
4646
deps = ["mypy"]
47-
commands = [["mypy", { replace = "posargs", default = ["src", "tests"]} ]]
47+
commands = [["mypy", { replace = "posargs", default = ["src", "tests"], extend = true} ]]
4848
4949
5050
.. tab:: INI
@@ -133,18 +133,18 @@ When ``<env_name>`` is the name of a specific environment, test environment conf
133133
"pytest>=8",
134134
"pytest-sugar"
135135
]
136-
commands = [["pytest", { replace = "posargs", default = ["tests"] }]]
136+
commands = [["pytest", { replace = "posargs", default = ["tests"], extend = true }]]
137137
138138
[env.lint]
139139
description = "run linters"
140140
skip_install = true
141141
deps = ["black"]
142-
commands = [["black", { replace = "posargs", default = ["."]} ]]
142+
commands = [["black", { replace = "posargs", default = ["."], extend = true} ]]
143143
144144
[env.type]
145145
description = "run type checks"
146146
deps = ["mypy"]
147-
commands = [["mypy", { replace = "posargs", default = ["src", "tests"]} ]]
147+
commands = [["mypy", { replace = "posargs", default = ["src", "tests"], extend = true} ]]
148148
149149
.. tab:: INI
150150

@@ -217,7 +217,7 @@ Basic example
217217
"pytest>=7",
218218
"pytest-sugar",
219219
]
220-
commands = [[ "pytest", "tests", { replace = "posargs"} ]]
220+
commands = [[ "pytest", "tests", { replace = "posargs", extend = true} ]]
221221
222222
.. tab:: INI
223223

src/tox/config/loader/convert.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,8 @@ def _to_typing(self, raw: T, of_type: type[V], factory: Factory[V]) -> V: # noq
5858
if origin in {list, List}:
5959
entry_type = of_type.__args__[0] # type: ignore[attr-defined]
6060
result = [self.to(i, entry_type, factory) for i in self.to_list(raw, entry_type)]
61+
if isclass(entry_type) and issubclass(entry_type, Command):
62+
result = [i for i in result if i is not None]
6163
elif origin in {set, Set}:
6264
entry_type = of_type.__args__[0] # type: ignore[attr-defined]
6365
result = {self.to(i, entry_type, factory) for i in self.to_set(raw, entry_type)}
@@ -160,7 +162,7 @@ def to_path(value: T) -> Path:
160162

161163
@staticmethod
162164
@abstractmethod
163-
def to_command(value: T) -> Command:
165+
def to_command(value: T) -> Command | None:
164166
"""
165167
Convert to a command to execute.
166168

src/tox/config/loader/memory.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ def to_path(value: Any) -> Path:
5151
return Path(value)
5252

5353
@staticmethod
54-
def to_command(value: Any) -> Command:
54+
def to_command(value: Any) -> Command | None:
5555
if isinstance(value, Command):
5656
return value
5757
if isinstance(value, str):

src/tox/config/loader/str_convert.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ def _win32_process_path_backslash(value: str, escape: str, special_chars: str) -
7272
return "".join(result)
7373

7474
@staticmethod
75-
def to_command(value: str) -> Command:
75+
def to_command(value: str) -> Command | None:
7676
"""
7777
At this point, ``value`` has already been substituted out, and all punctuation / escapes are final.
7878

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

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -96,8 +96,10 @@ def to_path(value: TomlTypes) -> Path:
9696
return Path(TomlLoader.to_str(value))
9797

9898
@staticmethod
99-
def to_command(value: TomlTypes) -> Command:
100-
return Command(args=cast(List[str], value)) # validated during load in _ensure_type_correct
99+
def to_command(value: TomlTypes) -> Command | None:
100+
if value:
101+
return Command(args=cast(List[str], value)) # validated during load in _ensure_type_correct
102+
return None
101103

102104
@staticmethod
103105
def to_env_list(value: TomlTypes) -> EnvList:

0 commit comments

Comments
 (0)