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
23 changes: 13 additions & 10 deletions fast_dash/Components.py
Original file line number Diff line number Diff line change
Expand Up @@ -1343,6 +1343,9 @@ def _get_component_from_input(hint, default_value=None):
# Resolve typing generics (Literal, Enum, Optional, Annotated, list[str], etc.)
resolved = _resolve_typing_hint(hint, default_value)
if resolved.component is not None:
resolved.component.tag = {"Literal": "Select", "Enum": "Select"}.get(
resolved.component.tag, resolved.component.tag
)
return resolved.component
hint = resolved.base_type

Expand Down Expand Up @@ -1396,7 +1399,7 @@ def _get_component_from_input(hint, default_value=None):

elif _default_value_type == "Sequence":
component = Fastify(
dmc.Select(data=default_value), "value", tag=_hint_type
dmc.Select(data=default_value), "value", tag="Select"
)

elif _default_value_type == "Dictionary":
Expand Down Expand Up @@ -1428,12 +1431,12 @@ def _get_component_from_input(hint, default_value=None):
component = Fastify(
dmc.NumberInput(value=hint(default_value)),
"value",
tag=_hint_type,
tag="NumberInput",
)

elif _default_value_type == "Numeric":
component = Fastify(
dmc.NumberInput(value=default_value), "value", tag=_hint_type
dmc.NumberInput(value=default_value), "value", tag="NumberInput"
)

elif _default_value_type == "Sequence":
Expand All @@ -1445,7 +1448,7 @@ def _get_component_from_input(hint, default_value=None):
)

component = Fastify(
dmc.Slider(min=start, max=stop, step=step), "value", tag=_hint_type
dmc.Slider(min=start, max=stop, step=step), "value", tag="Slider"
)

else:
Expand All @@ -1462,11 +1465,11 @@ def _get_component_from_input(hint, default_value=None):
tooltip={"placement": "bottom", "always_visible": True},
),
"value",
tag=_hint_type,
tag="Slider",
)

else:
component = Fastify(dmc.NumberInput(), "value", tag=_hint_type)
component = Fastify(dmc.NumberInput(), "value", tag="NumberInput")

elif _hint_type == "Sequence":
if _default_value_type == "Text":
Expand Down Expand Up @@ -1519,11 +1522,11 @@ def _get_component_from_input(hint, default_value=None):
# old dbc.Checkbox rendered with the Bootswatch accent (off-theme).
if _default_value_type == "Boolean":
component = Fastify(
dmc.Checkbox(checked=default_value), "checked", tag=_hint_type
dmc.Checkbox(checked=default_value), "checked", tag="Switch"
)

else:
component = Fastify(dmc.Checkbox(), "checked", tag=_hint_type)
component = Fastify(dmc.Checkbox(), "checked", tag="Switch")

elif _hint_type == "Image":
if _default_value_type == "Image":
Expand Down Expand Up @@ -1579,7 +1582,7 @@ def _get_component_from_input(hint, default_value=None):
style={"width": "100%"},
),
"date",
tag=_hint_type,
tag="DateInput",
)

else:
Expand All @@ -1590,7 +1593,7 @@ def _get_component_from_input(hint, default_value=None):
style={"width": "100%"},
),
"date",
tag=_hint_type,
tag="DateInput",
)

else:
Expand Down
38 changes: 37 additions & 1 deletion tests/test_mcp.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,12 @@
import importlib.util
import json
import warnings
from typing import Annotated, Optional, Tuple # module-level for get_type_hints (#119, #132)
from typing import ( # module-level for get_type_hints (#119, #132)
Annotated,
Literal,
Optional,
Tuple,
)

import pandas as pd # noqa: F401 (module-level for any -> pd.DataFrame hints)
import plotly.graph_objects as go
Expand Down Expand Up @@ -657,6 +662,37 @@ def style(color: str = "#1c7ed6",
assert by_id["bio"]["tag"] == "TextArea"
assert by_id["name"]["tag"] == "Text"

def test_static_input_tags_match_component_types(self):
# #158: expose component types, not internal hint names such as Numeric
# or Boolean, so agents can cross-reference list_component_types().
def form(
plain: str = "hi",
choice: str = ["a", "b"],
literal: Literal["x", "y"] = "x",
flavor: Flavor = Flavor.VANILLA,
count: int = 1,
ratio: float = 0.5,
enabled: bool = True,
day: datetime.date = datetime.date(2024, 1, 1),
) -> str:
return plain

c = _client_for(FastDash(callback_fn=form, mcp_server=True))
legal = set(_call(c, "list_component_types")["types"])
by_id = {i["id"]: i for i in _call(c, "describe_app")["inputs"]}

assert {name: item["tag"] for name, item in by_id.items()} == {
"plain": "Text",
"choice": "Select",
"literal": "Select",
"flavor": "Select",
"count": "NumberInput",
"ratio": "NumberInput",
"enabled": "Switch",
"day": "DateInput",
}
assert {item["tag"] for item in by_id.values()} <= legal

def test_password_value_goes_in_but_never_comes_back(self):
# #151: the browser masks a PasswordInput; the unauthenticated /mcp route
# must not undo that by reading the credential back out in plain text.
Expand Down