Skip to content

Fix container from_string collation for a single allow_none value (#908)#945

Open
apoorvdarshan wants to merge 1 commit into
ipython:mainfrom
apoorvdarshan:fix/issue-908-container-from-string-allow-none
Open

Fix container from_string collation for a single allow_none value (#908)#945
apoorvdarshan wants to merge 1 commit into
ipython:mainfrom
apoorvdarshan:fix/issue-908-container-from-string-allow-none

Conversation

@apoorvdarshan

Copy link
Copy Markdown

Fixes #908.

Root cause

Container.from_string (traitlets/traitlets.py) turns a value that fails literal_eval into None, then passes it to validate:

try:
    test = literal_eval(s)
except Exception:
    test = None
return self.validate(None, test)

Before #885, Instance.validate(None, None) always raised (None is not a list/set/tuple), so DeferredConfigString.get_value / DeferredConfigList.get_value caught the error and let the original string "lie", which is later collated into a single-item list.

PR #885 added an allow_none short-circuit to Instance.validate:

if self.allow_none and value is None:
    return value

For an allow_none=True container this now accepts the substituted None instead of raising, so from_string returns None and the collation fallback never happens. A single --Class.trait=value therefore resolves to None instead of ['value'].

(The regression was bisected to #885 in the issue, and the mechanism was identified by @krassowski there.)

Reproduction

from traitlets.config import Application, Config
from traitlets.config.loader import DeferredConfigString
from traitlets import List

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)
print(repr(app.a_list))

Observed on current main (wrong):

None

Expected / after this fix (and the behaviour on 5.12.0, before #885):

['a_value']

Fix

In Container.from_string, when literal_eval fails, validate the original string instead of substituting None:

try:
    test = literal_eval(s)
except Exception:
    test = s
return self.validate(None, test)

A bare string is not a container, so validate raises exactly as it did before #885 (independent of allow_none), and DeferredConfig.get_value falls back to collating the value into a single-item list. This is localized to the except branch and does not touch the allow_none handling in Instance.validate; the successful-parse path (including the "None" convention already covered by from_string_list) is unchanged.

Tests

Added to tests/test_traitlets.py:

  • test_container_from_string_single_value_allow_none (parametrized over List, Set, Tuple): asserts from_string("a_value") on an allow_none=True container raises TraitError rather than silently returning None.
  • test_list_allow_none_single_cli_value_is_collated: end-to-end check that a single deferred config value resolves to ['a_value'].

Both tests fail on unmodified main (the end-to-end one fails with assert None == ['a_value']) and pass with the fix. The full existing suite passes (591 passed, 1 skipped), and ruff check, ruff format --check, and mypy are clean on the changed files.

Disclosure: prepared with AI assistance; reviewed and verified locally.

Container.from_string substituted None for a value that failed
literal_eval, then passed it to validate. Since PR ipython#885 added an
allow_none short-circuit to Instance.validate, that None is now
accepted for allow_none containers instead of raising, so a single
--Class.trait=value (e.g. List(allow_none=True)) resolves to None
instead of ['value'].

Validate the original string on parse failure instead of None. A bare
string is not a container, so validation raises as it did before ipython#885,
letting DeferredConfig.get_value fall back to collating the value into
a single-item list.

Fixes ipython#908
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Collating list traits fails when only one is given - regression in traitlets 5.13.0

1 participant