From 0c36a9477ba71bd026a1d06ef12fac1a072b579e Mon Sep 17 00:00:00 2001 From: Thor Whalen <1906276+thorwhalen@users.noreply.github.com> Date: Tue, 20 Oct 2026 18:26:58 +0000 Subject: [PATCH] param_default: treat any i2 placeholder default as no default Use i2.is_placeholder_default (NotSet or any i2.DefaultPlaceholder, e.g. the marker methodize(..., default_from=...) puts in a signature), falling back to is_not_set / NotSet for older i2. Closes #50. Co-Authored-By: Claude Opus 5.5 --- front/util.py | 27 +++++++++++++++---------- tests/test_not_set_defaults.py | 37 ++++++++++++++++++++++++++++++++++ 2 files changed, 53 insertions(+), 11 deletions(-) diff --git a/front/util.py b/front/util.py index ed8adaa..4667358 100644 --- a/front/util.py +++ b/front/util.py @@ -24,21 +24,26 @@ from front.types import Map try: - from i2 import is_not_set -except ImportError: # older i2: same sentinel, not exported from the root yet - from i2.deco import NotSet as _NotSet + from i2 import is_placeholder_default +except ImportError: # older i2: no DefaultPlaceholder yet, so only NotSet is a placeholder + try: + from i2 import is_not_set as is_placeholder_default + except ImportError: # older still: same sentinel, not exported from the root yet + from i2.deco import NotSet as _NotSet - def is_not_set(x) -> bool: - """Return True iff ``x`` is ``i2``'s ``NotSet`` sentinel.""" - return x is _NotSet + def is_placeholder_default(x) -> bool: + """Return True iff ``x`` is ``i2``'s ``NotSet`` sentinel.""" + return x is _NotSet def param_default(param): - """Return ``param.default``, or ``Parameter.empty`` if it is ``i2``'s ``NotSet``. + """Return ``param.default``, or ``Parameter.empty`` if it is a placeholder default. - ``NotSet`` in a signature means "no value given", not a real default, so UI and - schema builders must treat that param as required (no prefill, no type inference - from the default). + A placeholder default is ``i2``'s ``NotSet`` sentinel or any + ``i2.DefaultPlaceholder`` (such as the marker ``methodize(..., default_from=...)`` + puts in a signature). It means "no concrete value here", not a real default, so UI + and schema builders must treat that param as required (no prefill, no type + inference from the default). >>> from inspect import Parameter >>> from i2.deco import NotSet @@ -50,7 +55,7 @@ def param_default(param): """ default = param.default - return Parameter.empty if is_not_set(default) else default + return Parameter.empty if is_placeholder_default(default) else default ignore_import_problems = suppress(ImportError, ModuleNotFoundError) diff --git a/tests/test_not_set_defaults.py b/tests/test_not_set_defaults.py index 3669730..b78899a 100644 --- a/tests/test_not_set_defaults.py +++ b/tests/test_not_set_defaults.py @@ -107,3 +107,40 @@ def changes(func): ) assert changes(_foo_with_not_set) == changes(_foo) == {} + + +try: + from i2 import is_placeholder_default as _ # noqa: F401 + + _HAS_PLACEHOLDERS = True +except ImportError: + _HAS_PLACEHOLDERS = False + + +@pytest.mark.skipif(not _HAS_PLACEHOLDERS, reason="i2 without DefaultPlaceholder") +def test_methodize_default_from_placeholder_is_no_default(): + """i2mint/front#50: a ``methodize(..., default_from=...)`` default is a marker, + not a value: treat it like ``NotSet`` (required, no prefill, no pydantic default). + """ + from i2.wrapper import methodize + + def foo(x, y: int, z: int, v: int = 1): + return x + y * z + v + + @dataclass + class K: + x: int + zoo: int = 2 + bar = methodize(foo, bind="x", default_from=[("z", "zoo")]) + + params = Sig(K(1).bar).parameters + assert params["z"].default is not Parameter.empty # the marker really is there + assert [param_default(p) for p in params.values()] == [Parameter.empty] * 2 + [1] + + specs = dict(func_to_pyd_model_specs(K(1).bar)) + assert specs["z"] == (int, ...) + assert specs["v"] == (int, 1) + + element, state = _render_input(IntInputBase, params["z"]) + assert element.view_value == 0 + assert element.value.get() is not params["z"].default