-
-
Notifications
You must be signed in to change notification settings - Fork 549
Tox config parser doesn't properly validate nested complex types like EnvList #3898
Description
Issue
tox-gh registers its config value like this:
self.add_config(
"python",
of_type=dict[str, EnvList],
default={},
desc="python version to mapping",
)this appears to work fine at first glance if you use a simple configuration, but as soon as you would like to use a product and possible a prefix, it no longer works, because the validate function handles extra types by simply calling their constructor, instead of using TomlLoader.to_env_list.
Environment
Provide at least:
- OS: OpenSUSE Tumbleweed
Output of pip list of the host Python, where tox is installed
Using Python 3.14.3 environment at: venv
Package Version Editable project location
------------------ --------------- -------------------------
annotated-types 0.7.0
ansible 13.4.0
ansible-core 2.20.4
anyio 4.13.0
bandit 1.9.4
bcrypt 5.0.0
bracex 2.6
bump-my-version 1.3.0
cachetools 7.0.5
certifi 2026.2.25
cffi 2.0.0
cfgv 3.5.0
charset-normalizer 3.4.6
click 8.3.1
colorama 0.4.6
coverage 7.13.5
cryptography 46.0.5
distlib 0.4.0
filelock 3.25.2
gitdb 4.0.12
gitpython 3.1.46
h11 0.16.0
httpcore 1.0.9
httpx 0.28.1
identify 2.6.18
idna 3.11
iniconfig 2.3.0
invoke 2.2.1
jinja2 3.1.6
librt 0.8.1
markdown-it-py 4.0.0
markupsafe 3.0.3
mdurl 0.1.2
mitogen 0.3.44
mypy 1.19.1
mypy-extensions 1.1.0
nodeenv 1.10.0
packaging 26.0
paramiko 4.0.0
pathspec 1.0.4
pip 25.3
platformdirs 4.9.4
pluggy 1.6.0
port-for 1.0.0
prek 0.3.8
prompt-toolkit 3.0.52
pycparser 3.0
pydantic 2.12.5
pydantic-core 2.41.5
pydantic-settings 2.13.1
pygments 2.19.2
pynacl 1.6.2
pyproject-api 1.10.0
pytest 9.0.2
pytest-codecov 0.7.0
pytest-cov 7.1.0
python-discovery 1.2.0
python-dotenv 1.2.2
pyyaml 6.0.3
questionary 2.1.1
requests 2.32.5
resolvelib 1.2.1
rich 14.3.3
rich-click 1.9.7
ruff 0.15.7
smmap 5.0.3
stevedore 5.7.0
suitable 0.22.0
tomli-w 1.2.0
tomlkit 0.14.0
tox 4.50.3
tox-gh 1.7.1
tox-uv 1.33.4
tox-uv-bare 1.33.4
types-paramiko 4.0.0.20260322
types-pyyaml 6.0.12.20250915
typing-extensions 4.15.0
typing-inspection 0.4.2
urllib3 2.6.3
uv 0.11.1
virtualenv 21.2.0
wcmatch 10.1
wcwidth 0.6.0Output of running tox
Output of tox -rvv
ROOT: 241 W running tox-gh [tox_gh/plugin.py:87]
Traceback (most recent call last):
File "/home/david/git/suitable/venv/lib64/python3.14/site-packages/tox/config/loader/toml/_validate.py", line 67, in validate
of_type(val)
~~~~~~~^^^^^
File "/home/david/git/suitable/venv/lib64/python3.14/site-packages/tox/config/types.py", line 70, in __init__
self.envs = list(OrderedDict((e, None) for e in envs).keys())
~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^
TypeError: unhashable type: 'dict'
ROOT: 242 E HandledError| failed to load core.python: [{'product': [['py311'], ['ansible9', 'ansible10', 'ansible11', 'ansible12']]}] is not of type 'EnvList' [tox/run.py:26]Minimal example
Put something like the following in a tox.toml and try to run tox with tox-gh and GITHUB_ACTIONS=true
[gh.python]
"3.12" = [
{ product = [["py312"], { prefix = "ansible", start = 9, stop = 13 }] },
"ruff",
"bandit",
"mypy"
]Suggested solution
Since Convert._to_typing calls self.to again for each key and value, I think the easiest thing to do is to just remove the recursive validation, since you end up validating each key and value twice, once before conversion and once after conversion. Which doesn't really make a ton of sense.
So this
elif casting_to in {dict, dict}:
key_type, value_type = type_args[0], type_args[1]
if isinstance(val, dict):
for va in val:
validate(va, key_type)
for va in val.values():
validate(va, value_type)
else:
msg = f"{val!r} is not dictionary"Alternatively you can do the workaround that's currently in place for Command and validate EnvList
should simplify to this:
elif casting_to in {dict, dict}:
key_type, value_type = type_args[0], type_args[1]
if not isinstance(val, dict):
msg = f"{val!r} is not dictionary"The same simplfication can be applied to the branch for lists. Alternatively you can do something similar like with Command and add a new branch to validate that skips deep validation for EnvList, since to_env_list already performs its own validation.