Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add the ability to put items in an expander by annotating Pydantic model fields #64

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
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
30 changes: 30 additions & 0 deletions examples/expanders.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
"""
An example of using the Expander annotation with nested objects.
"""

from typing_extensions import Annotated, List
from pydantic import BaseModel
import streamlit as st
import streamlit_pydantic as sp
from streamlit_pydantic import Expander


class Child(BaseModel):
"""Child class."""

name: str
age: int


class Parent(BaseModel):
"""Parent class."""

occupation: str
child: Annotated[List[Child], Expander]


data = sp.pydantic_input("form", model=Parent)

if data:
obj = Parent.model_validate(data)
st.json(obj.model_dump())
2 changes: 2 additions & 0 deletions src/streamlit_pydantic/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,5 @@
from .ui_renderer import pydantic_output as _pydantic_output

pydantic_output = st._gather_metrics("pydantic_output", _pydantic_output)

from .annotations import Expander
8 changes: 8 additions & 0 deletions src/streamlit_pydantic/annotations.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
"""
Annotations to be used within Pydantic objects.
"""

from typing import Annotated, TypeVar


Expander = TypeVar("Expander")
73 changes: 59 additions & 14 deletions src/streamlit_pydantic/ui_renderer.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@

from streamlit_pydantic import schema_utils

from .annotations import Expander


_OVERWRITE_STREAMLIT_KWARGS_PREFIX = "st_kwargs_"


Expand Down Expand Up @@ -168,6 +171,10 @@ def render_ui(self) -> Dict:

property = self._schema_properties[property_key]

expander = Expander in self._input_class.model_fields[property_key].metadata

property["expander"] = expander

if not property.get("title"):
# Set property key as fallback title
property["title"] = _name_to_title(property_key)
Expand All @@ -185,10 +192,25 @@ def render_ui(self) -> Dict:
if attr is not None:
property["instance_class"] = str(type(attr))

try:
value = self._render_property(streamlit_app, property_key, property)
def _render_prop():
value = self._render_property(
streamlit_app, property_key, property
)
if not self._is_value_ignored(property_key, value):
self._store_value(property_key, value)

try:
if expander and not schema_utils.is_object_list_property(
property, self._schema_references
):
with self._streamlit_container.expander(
property_key, expanded=False
):
_render_prop()

else:
_render_prop()

except Exception:
pass

Expand Down Expand Up @@ -1012,7 +1034,9 @@ def _render_dict_clear_button(

return data_dict

def _render_list_input(self, streamlit_app: Any, key: str, property: Dict) -> Any:
def _render_list_input(
self, streamlit_app: Any, key: str, property: Dict
) -> Any:
# Add title and subheader
streamlit_app.subheader(property.get("title"))
if property.get("description"):
Expand Down Expand Up @@ -1041,27 +1065,48 @@ def _render_list_input(self, streamlit_app: Any, key: str, property: Dict) -> An
if self._clear_button_allowed(property):
data_list = self._render_list_clear_button(key, clear_col, data_list)

remove_inds = []

def _render_item(index, item):
output = self._render_list_item(
streamlit_app,
key,
item,
index,
property,
)
if output is not None:
object_list.append(output)

else:
remove_inds.append(index)

if is_object:
streamlit_app.markdown("---")

if len(data_list) > 0:
for index, item in enumerate(data_list):
output = self._render_list_item(
streamlit_app,
key,
item,
index,
property,
)
if output is not None:
object_list.append(output)
if "expander" in property and property["expander"]:
with self._streamlit_container.expander(
property["title"], expanded=False
):
_render_item(index, item)

if is_object:
streamlit_app.markdown("---")
else:
_render_item(index, item)

if not self._add_button_allowed(len(object_list), property):
add_col = add_col.empty()

if not is_object:
streamlit_app.markdown("---")

for ind in reversed(remove_inds):
data_list.pop(ind)

if len(remove_inds) > 0:
st.rerun()

return object_list

def _render_property(self, streamlit_app: Any, key: str, property: Dict) -> Any:
Expand Down