Skip to content
22 changes: 22 additions & 0 deletions tesseract_streamlit/parse.py
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,14 @@ def _is_scalar(shape_dict: dict[str, typing.Any]) -> bool:
return False


class NumberConstraints(typing.TypedDict):
"""Container for constraints on scalar numeric values."""

min_value: NotRequired[float]
max_value: NotRequired[float]
step: NotRequired[float]


class _InputField(typing.TypedDict):
"""Simplified schema for an input field in the Streamlit template.

Expand All @@ -273,6 +281,7 @@ class _InputField(typing.TypedDict):
description: str
ancestors: list[str]
default: NotRequired[typing.Any]
number_constraints: NumberConstraints


def _key_to_title(key: str) -> str:
Expand Down Expand Up @@ -305,19 +314,31 @@ def _format_field(
description=field_data.get("description", None),
ancestors=[*ancestors, field_key],
)

if "properties" not in field_data: # signals a Python primitive type
if field["type"] != "object":
default_val = field_data.get("default", None)
if (field_data["type"] == "string") and (default_val is None):
default_val = ""
field["default"] = default_val
if field_data["type"] in ("number", "integer"):
field["number_constraints"] = {
"min_value": field_data.get("minimum", None),
"max_value": field_data.get("maximum", None),
"step": field_data.get("multipleOf", None),
}
return field
field["title"] = _key_to_title(field_key) if use_title else field_key
if ARRAY_PROPS <= set(field_data["properties"]):
data_type = "array"
if _is_scalar(field_data["properties"]["shape"]):
data_type = "number"
field["default"] = field_data.get("default", None)
field["number_constraints"] = {
"min_value": field_data.get("minimum", None),
"max_value": field_data.get("maximum", None),
"step": field_data.get("multipleOf", None),
}
field["type"] = data_type
return field
# at this point, not an array or primitive, so must be composite
Expand Down Expand Up @@ -404,6 +425,7 @@ class JinjaField(typing.TypedDict):
description: str
title: str
default: NotRequired[typing.Any]
number_constraints: NumberConstraints


def _input_to_jinja(field: _InputField) -> JinjaField:
Expand Down
7 changes: 6 additions & 1 deletion tesseract_streamlit/templates/template.j2
Original file line number Diff line number Diff line change
Expand Up @@ -84,14 +84,19 @@ st.markdown({% if not udf.docs %}""{% else %}
{% elif field.type == 'integer' %}
{{ field.uid }} = {{ field.container }}.number_input(
"{{ field.get('title', field.uid) }}",
step=1,
min_value=_cast_type({{ field.get("number_constraints", {}).get("min_value", None) }}, int),
max_value=_cast_type({{ field.get("number_constraints", {}).get("max_value", None) }}, int),
step=_cast_type({{ field.get("number_constraints", {}).get("step", 1) }}, int),
format="%d",
key="int.{{ field.key }}",
value=_cast_type({{ field.get("default", None) }}, int),
)
{% elif field.type == 'number' %}
{{ field.uid }} = {{ field.container }}.number_input(
"{{ field.get('title', field.uid) }}",
min_value=_cast_type({{ field.get("number_constraints", {}).get("min_value", None) }}, float),
max_value=_cast_type({{ field.get("number_constraints", {}).get("max_value", None) }}, float),
step=_cast_type({{ field.get("number_constraints", {}).get("step", None) }}, float),
key="number.{{ field.key }}",
format="%f",
value=_cast_type({{ field.get("default", None) }}, float),
Expand Down
16 changes: 12 additions & 4 deletions tests/goodbyeworld/tesseract_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,27 @@
class Hobby(BaseModel):
name: str = Field(description="Name of the activity.")
active: bool = Field(description="Does the person actively engage with it?")
experience: int = Field(description="Experience practising it in years.")
experience: int = Field(description="Experience practising it in years.", minimum=0)


class InputSchema(BaseModel):
name: str = Field(
description="Name of the person you want to greet.", default="John Doe"
)
age: int = Field(description="Age of person in years.", default=30)
height: float = Field(description="Height of person in cm.", default=175.0)
age: int = Field(
description="Age of person in years.", default=30, minimum=0, maximum=125
)
height: float = Field(
description="Height of person in cm.",
default=175.0,
minimum=0,
maximum=300.0,
multiple_of=0.1,
)
alive: bool = Field(
description="Whether the person is (currently) alive.", default=True
)
weight: Float32 = Field(description="The person's weight in kg.")
weight: Float32 = Field(description="The person's weight in kg.", minimum=0.0)
leg_lengths: Array[(2,), Float32] = Field(
description="The length of the person's left and right legs in cm."
)
Expand Down
114 changes: 113 additions & 1 deletion tests/mock-schema-fields.json
Comment thread
jacanchaplais marked this conversation as resolved.
Resolved
Original file line number Diff line number Diff line change
@@ -1 +1,113 @@
[{"type":"string","title":"Name","description":"Name of the person you want to greet.","ancestors":["name"],"default":"John Doe"},{"type":"integer","title":"Age","description":"Age of person in years.","ancestors":["age"],"default":30},{"type":"number","title":"Height","description":"Height of person in cm.","ancestors":["height"],"default":175.0},{"type":"boolean","title":"Alive","description":"Whether the person is (currently) alive.","ancestors":["alive"],"default":true},{"type":"number","title":"Weight","description":"The person's weight in kg.","ancestors":["weight"],"default":null},{"type":"array","title":"Leg Lengths","description":"The length of the person's left and right legs in cm.","ancestors":["leg_lengths"]},{"type":"composite","title":"Hobby","description":"The person's only hobby.","ancestors":["hobby"]},{"type":"string","title":"Name","description":"Name of the activity.","ancestors":["hobby","name"],"default":""},{"type":"boolean","title":"Active","description":"Does the person actively engage with it?","ancestors":["hobby","active"],"default":null},{"type":"integer","title":"Experience","description":"Experience practising it in years.","ancestors":["hobby","experience"],"default":null}]
[
{
"type": "string",
"title": "Name",
"description": "Name of the person you want to greet.",
"ancestors": [
"name"
],
"default": "John Doe"
},
{
"type": "integer",
"title": "Age",
"description": "Age of person in years.",
"ancestors": [
"age"
],
"default": 30,
"number_constraints": {
"max_value": 125,
"min_value": 0,
"step": null
}
},
{
"type": "number",
"title": "Height",
"description": "Height of person in cm.",
"ancestors": [
"height"
],
"default": 175,
"number_constraints": {
"max_value": 300,
"min_value": 0,
"step": 0.1
}
},
{
"type": "boolean",
"title": "Alive",
"description": "Whether the person is (currently) alive.",
"ancestors": [
"alive"
],
"default": true
},
{
"type": "number",
"title": "Weight",
"description": "The person's weight in kg.",
"ancestors": [
"weight"
],
"default": null,
"number_constraints": {
"max_value": null,
"min_value": 0,
"step": null
}
},
{
"type": "array",
"title": "Leg Lengths",
"description": "The length of the person's left and right legs in cm.",
"ancestors": [
"leg_lengths"
]
},
{
"type": "composite",
"title": "Hobby",
"description": "The person's only hobby.",
"ancestors": [
"hobby"
]
},
{
"type": "string",
"title": "Name",
"description": "Name of the activity.",
"ancestors": [
"hobby",
"name"
],
"default": ""
},
{
"type": "boolean",
"title": "Active",
"description": "Does the person actively engage with it?",
"ancestors": [
"hobby",
"active"
],
"default": null
},
{
"type": "integer",
"title": "Experience",
"description": "Experience practising it in years.",
"ancestors": [
"hobby",
"experience"
],
"default": null,
"number_constraints": {
"max_value": 120,
"min_value": 0,
"step": null
}
}
]
Loading