Skip to content
Merged
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
27 changes: 16 additions & 11 deletions front/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -50,7 +55,7 @@ def param_default(param):
<class 'inspect._empty'>
"""
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)
Expand Down
37 changes: 37 additions & 0 deletions tests/test_not_set_defaults.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Loading