Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions tests/test_traitlets.py
Original file line number Diff line number Diff line change
Expand Up @@ -3040,6 +3040,41 @@ def test_list_items_from_string(s, expected, value_trait):
_from_string_test(List(value_trait), s, expected)


@pytest.mark.parametrize("container", [List, Set, Tuple])
def test_container_from_string_single_value_allow_none(container):
"""Regression test for https://github.com/ipython/traitlets/issues/908.

``from_string`` receives a single, non-literal string. It must not
silently coerce it to ``None`` on ``allow_none`` containers: a bare
string is not a container, so validation must reject it. This lets the
config loader fall back to collating the value into a single-item list
(e.g. ``--Class.trait=value`` -> ``['value']``) instead of ``None``.
"""
trait = container(allow_none=True)
trait.name = "a_trait"
with pytest.raises(TraitError):
trait.from_string("a_value")


def test_list_allow_none_single_cli_value_is_collated():
"""End-to-end regression for issue #908.

``--MyApp.a_list=a_value`` with ``a_list = List(allow_none=True)`` must
yield ``['a_value']``, not ``None`` (regression introduced in #885).
"""
from traitlets.config import Application, Config
from traitlets.config.loader import DeferredConfigString

class MyApp(Application):
a_list = List(allow_none=True).tag(config=True)

config = Config()
config.MyApp.a_list = DeferredConfigString("a_value")
app = MyApp()
app.update_config(config)
assert app.a_list == ["a_value"]


@pytest.mark.parametrize(
"s, expected",
[
Expand Down
6 changes: 5 additions & 1 deletion traitlets/traitlets.py
Original file line number Diff line number Diff line change
Expand Up @@ -3530,7 +3530,11 @@ def from_string(self, s: str) -> T | None:
try:
test = literal_eval(s)
except Exception:
test = None
# Not a literal; validate the original string so it is rejected
# (a bare string is not a container) and callers can fall back to
# collating it into a single-item list. Substituting ``None`` here
# would be wrongly accepted by ``allow_none`` containers.
test = s
return self.validate(None, test)

def from_string_list(self, s_list: list[str]) -> T | None:
Expand Down
Loading