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
16 changes: 11 additions & 5 deletions streamlitfront/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,11 @@

from streamlitfront.spec_maker import SpecMaker
from streamlitfront.session_state import get_state, _SessionState
from streamlitfront.util import func_name, build_element_factory
from streamlitfront.util import (
func_name,
build_element_factory,
signature_defaults,
)

# --------------------- types/protocols/interfaces --------------------------------------

Expand Down Expand Up @@ -132,10 +136,11 @@ def dispatch_funcs(


def infer_type(sig, name):
defaults = signature_defaults(sig) # i2's NotSet sentinel is not a default
if name in sig.annotations:
return sig.annotations[name]
elif name in sig.defaults:
dflt = sig.defaults[name]
elif name in defaults:
dflt = defaults[name]
if dflt is not None:
return type(dflt)
else:
Expand Down Expand Up @@ -189,6 +194,7 @@ def get_func_args_specs(
element_factory_for_annot or _get_dflt_element_factory_for_annot()
)
sig = Sig(func)
defaults = signature_defaults(sig) # i2's NotSet sentinel is not a default
func_args_specs = {name: {} for name in sig.names}
for name in sig.names:
d = func_args_specs[name]
Expand All @@ -200,8 +206,8 @@ def get_func_args_specs(
missing,
dflt_element_factory,
)
if name in sig.defaults:
dflt = sig.defaults[name]
if name in defaults:
dflt = defaults[name]
if dflt is not None:
# TODO: type-to-element conditions must be in configs
if isinstance(dflt, (list, tuple, set)):
Expand Down
22 changes: 15 additions & 7 deletions streamlitfront/page_funcs.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,12 @@
BasePageFunc,
_get_dflt_element_factory_for_annot,
)
from streamlitfront.util import build_factory, Command, NodeGetter
from streamlitfront.util import (
build_factory,
Command,
NodeGetter,
signature_defaults,
)


# TODO: Extract the page setup (view_title, etc.) and make it injectable.
Expand All @@ -30,6 +35,7 @@ def get_func_elements_commands(
element_factory_for_annot or _get_dflt_element_factory_for_annot()
)
sig = Sig(func)
defaults = signature_defaults(sig) # i2's NotSet sentinel is not a default
NodeGetter(state["views"])

func_args_specs = {name: {} for name in sig.names}
Expand All @@ -46,8 +52,8 @@ def get_func_elements_commands(
dflt_element_factory,
)

if name in sig.defaults:
dflt = sig.defaults[name]
if name in defaults:
dflt = defaults[name]
if dflt is not None:
# TODO: type-to-element conditions must be in configs
if isinstance(dflt, (list, tuple, set)):
Expand All @@ -68,6 +74,7 @@ def get_func_args_specs(
element_factory_for_annot or _get_dflt_element_factory_for_annot()
)
sig = Sig(func)
defaults = signature_defaults(sig) # i2's NotSet sentinel is not a default
func_args_specs = {name: {} for name in sig.names}
for name in sig.names:
d = func_args_specs[name]
Expand All @@ -79,8 +86,8 @@ def get_func_args_specs(
missing,
dflt_element_factory,
)
if name in sig.defaults:
dflt = sig.defaults[name]
if name in defaults:
dflt = defaults[name]
if dflt is not None:
# TODO: type-to-element conditions must be in configs
if isinstance(dflt, (list, tuple, set)):
Expand Down Expand Up @@ -127,6 +134,7 @@ def special_get_func_args_specs(
element_factory_for_annot or _get_dflt_element_factory_for_annot()
)
sig = Sig(func)
defaults = signature_defaults(sig) # i2's NotSet sentinel is not a default
func_args_specs = {name: {} for name in sig.names}
for name in sig.names:
d = func_args_specs[name]
Expand All @@ -138,8 +146,8 @@ def special_get_func_args_specs(
missing,
dflt_element_factory,
)
if name in sig.defaults:
dflt = sig.defaults[name]
if name in defaults:
dflt = defaults[name]
if dflt is not None:
# TODO: type-to-element conditions must be in configs
if isinstance(dflt, (list, tuple, set)):
Expand Down
39 changes: 39 additions & 0 deletions streamlitfront/tests/test_not_set_defaults.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
"""``i2``'s ``NotSet`` sentinel in a signature means "required / no default".

See i2mint/i2#48: once ``i2.FuncFactory`` shows ``NotSet`` defaults, widgets must not
be prefilled with the sentinel, nor typed after it. These tests use
``i2.deco.NotSet`` directly, so they pass with any i2 version.
"""

from i2 import Sig
from i2.deco import NotSet

from streamlitfront import base, page_funcs


def foo(a: int, b, c: str = "hi", d=2):
return a, b, c, d


# ``foo`` with ``NotSet`` defaults, as a re-landed i2#88 ``FuncFactory`` would show.
# Defined separately since ``Sig.__call__`` sets ``__signature__`` in place.
def foo_with_not_set(a: int = NotSet, b=NotSet, c: str = "hi", d=2):
return a, b, c, d


def test_infer_type_ignores_not_set():
for name in "abcd":
assert base.infer_type(Sig(foo_with_not_set), name) is base.infer_type(
Sig(foo), name
)


def test_func_args_specs_are_not_prefilled_with_not_set():
for get_specs in (
base.get_func_args_specs,
page_funcs.get_func_args_specs,
page_funcs.special_get_func_args_specs,
):
assert get_specs(foo_with_not_set) == get_specs(foo)
_, factory_kwargs = get_specs(foo_with_not_set)["a"]["element_factory"]
assert "value" not in factory_kwargs
23 changes: 23 additions & 0 deletions streamlitfront/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,29 @@
from i2.signatures import Sig, name_of_obj
from i2._deprecated import Command as _Command

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

def is_not_set(x) -> bool:
"""Return True iff ``x`` is ``i2``'s ``NotSet`` sentinel."""
return x is _NotSet


def signature_defaults(sig) -> dict:
"""Return ``sig.defaults`` without the params whose default is ``i2``'s ``NotSet``.

``NotSet`` in a signature means "no value given", not a real default, so a widget
must not be prefilled with it, nor typed after it.

>>> from i2.deco import NotSet
>>> def foo(a, b=NotSet, c=3): ...
>>> signature_defaults(Sig(foo))
{'c': 3}
"""
return {k: v for k, v in sig.defaults.items() if not is_not_set(v)}


# TODO: Consider using functools.partial (or subclass thereof) instead of Command
class Command(_Command):
Expand Down
Loading