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
61 changes: 58 additions & 3 deletions streamlitfront/elements/elements.py
Original file line number Diff line number Diff line change
Expand Up @@ -183,13 +183,27 @@ def render(self):
self._render_section_content()

def _render_section_content(self):
# Only the uploaders drawn in *this* run have a slot to redraw into.
for uploader in _file_uploaders(self.children):
uploader._slot = None
inputs = self._render_inputs()
if self.auto_submit or st.button(self.submit_button_label):
try:
self._submit(inputs)
except ValidationError as e:
st.error(e)

def _submit(self, inputs):
super()._submit(inputs)
if not self.auto_submit:
self._clear_file_uploaders()

def _clear_file_uploaders(self):
"""Empty the file uploaders that asked for it."""
for uploader in _file_uploaders(self.children):
if getattr(uploader, "clear_after_submit", True):
uploader.clear()

def _noneable(self, input_instance: InputBase) -> InputBase:
# input_render = input_instance.render
input_render = getattr(input_instance, "render")
Expand Down Expand Up @@ -262,15 +276,56 @@ def render(self):
class FileUploader(FileUploaderBase):
display_label: bool = True

def render(self):
label_visibility = "visible" if self.display_label else "collapsed"
@property
def _generation_key(self) -> str:
return self._build_key(f"{self.name}_upload_generation")

def clear(self):
"""Empty the uploader, now and on the following runs.

Streamlit keeps an uploader's files for as long as its widget key lives and
offers no other way to empty it. So: move to a new key, and draw a fresh
uploader in the slot of the old one right away, so that this run's output and
everything else drawn stay in place (no rerun).
"""
self._generation += 1
slot = getattr(self, "_slot", None)
if slot is not None:
with slot:
self._file_uploader()

@property
def _generation(self) -> int:
return st.session_state.get(self._generation_key, 0)

@_generation.setter
def _generation(self, value: int):
st.session_state[self._generation_key] = value

def _file_uploader(self):
return st.file_uploader(
label=self.name,
label_visibility=label_visibility,
label_visibility="visible" if self.display_label else "collapsed",
type=self.type,
accept_multiple_files=self.accept_multiple_files,
key=f"{self.view_key}_{self.name}_{self._generation}",
)

def render(self):
self._slot = st.empty()
with self._slot:
return self._file_uploader()


def _file_uploaders(components) -> Iterable[FileUploader]:
"""The ``FileUploader`` inputs among ``components`` (looking inside multi-source
inputs)."""
for component in components:
if isinstance(component, MultiSourceInputBase):
yield from _file_uploaders(component.input_components)
elif isinstance(component, FileUploader):
yield component


class AudioRecorder(InputBase):
def render(self):
Expand Down
16 changes: 9 additions & 7 deletions streamlitfront/examples/crude.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ def bar(foo_output: float):
return foo_output * 10


if __name__ == '__main__':
if __name__ == "__main__":
# param_to_mall_map = dict(a='a', b='b_store')

if not b.mall():
Expand Down Expand Up @@ -58,17 +58,19 @@ def bar(foo_output: float):
app = mk_app(
[foo, bar],
config={
APP_KEY: {'title': 'Crude App'},
APP_KEY: {"title": "Crude App"},
# OBJ_KEY: {'trans': crudify},
RENDERING_KEY: {
Callable: {
'execution': {
'inputs': {
'save_name': {
"execution": {
"inputs": {
"save_name": {
ELEMENT_KEY: TextInput,
NAME_KEY: 'Save output as',
NAME_KEY: "Save output as",
},
str: {
ELEMENT_KEY: SelectBox,
},
str: {ELEMENT_KEY: SelectBox,},
}
},
# 'code': {
Expand Down
30 changes: 16 additions & 14 deletions streamlitfront/examples/custom_output.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@ class HitTheOutputValue(OutputBase):
margin: int = 20

def render(self):
template = f'{self.name}: {self.output}. {{0}}'
template = f"{self.name}: {self.output}. {{0}}"
if self.output == self.value_to_hit:
st.success(template.format('You hit it! Congratulations!'))
st.success(template.format("You hit it! Congratulations!"))
st.balloons()
elif abs(self.output - self.value_to_hit) <= self.margin:
st.warning(template.format("You're warm..."))
Expand All @@ -30,28 +30,30 @@ def render(self):
st.snow()


if __name__ == '__main__':
if __name__ == "__main__":
app = mk_app(
[foo],
config={
APP_KEY: {'title': 'My Custom Output'},
APP_KEY: {"title": "My Custom Output"},
RENDERING_KEY: {
'foo': {
NAME_KEY: 'Hit the value',
'description': {
'content': '''
"foo": {
NAME_KEY: "Hit the value",
"description": {
"content": """
Try to hit the secret output value by playing with the inputs. \n
Hint: It is the answer to the ultimate question of life, the universe, and everything.
''',
""",
},
'execution': {
'output': {
"execution": {
"output": {
ELEMENT_KEY: HitTheOutputValue,
'value_to_hit': 42,
'margin': 10,
"value_to_hit": 42,
"margin": 10,
},
},
'code': {ELEMENT_KEY: SourceCodeSection,},
"code": {
ELEMENT_KEY: SourceCodeSection,
},
}
},
},
Expand Down
16 changes: 8 additions & 8 deletions streamlitfront/examples/dag_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@


def b(a: int):
return 2 ** a
return 2**a


def d(c: int):
return 10 - (5 ** c)
return 10 - (5**c)


def result(b, d):
Expand All @@ -19,13 +19,13 @@ def result(b, d):

dag = DAG((b, d, result))

if __name__ == '__main__':
if __name__ == "__main__":
# Have special element to indicate removal of field in recursive mapping merge

app = mk_app(
[dag],
config={
APP_KEY: {'title': 'DAG App'},
APP_KEY: {"title": "DAG App"},
RENDERING_KEY: {
DAG: {
# Here, we add a new home made element. Note that this "Graph" element
Expand All @@ -34,12 +34,12 @@ def result(b, d):
# along with its parameters.
# You can do exactly the same at every level of the tree by defining
# your own elements and include them at the right place in the configuration.
'graph': {
"graph": {
ELEMENT_KEY: Graph,
NAME_KEY: 'Flow',
'use_container_width': True,
NAME_KEY: "Flow",
"use_container_width": True,
},
'description': {'content': ''},
"description": {"content": ""},
}
},
},
Expand Down
87 changes: 43 additions & 44 deletions streamlitfront/examples/data_binding.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,26 +29,25 @@
from streamlitfront import mk_app, binder as b
from streamlitfront.elements import SelectBox


data = {
'Pink Floyd': {
'David Gilmour': 'Guitar',
'Roger Waters': 'Bass Guitar',
'Syd Barrett': 'Guitar',
'Richard Wright': 'Keyboards',
'Nick Mason': 'Drums',
"Pink Floyd": {
"David Gilmour": "Guitar",
"Roger Waters": "Bass Guitar",
"Syd Barrett": "Guitar",
"Richard Wright": "Keyboards",
"Nick Mason": "Drums",
},
'The Doors': {
'Jim Morrison': 'Lead vocalist',
'Ray Manzarek': 'Keyboards',
'Robby Krieger': 'Guitar',
'John Densmore': 'Drums',
"The Doors": {
"Jim Morrison": "Lead vocalist",
"Ray Manzarek": "Keyboards",
"Robby Krieger": "Guitar",
"John Densmore": "Drums",
},
'The Beatles': {
'John Lennon': 'Guitar',
'Paul McCartney': 'Bass Guitar',
'George Harrison': 'Guitar',
'Ringo Starr': 'Drums',
"The Beatles": {
"John Lennon": "Guitar",
"Paul McCartney": "Bass Guitar",
"George Harrison": "Guitar",
"Ringo Starr": "Drums",
},
}

Expand All @@ -69,7 +68,7 @@ def get_instrument_from_member_only(member: str):

def get_members_from_instrument(instrument: str):
return [
f'{m} ({band})'
f"{m} ({band})"
for band, members in data.items()
for m, i in members.items()
if i == instrument
Expand Down Expand Up @@ -98,7 +97,7 @@ def init_selected():
on_select_band(band)


if __name__ == '__main__':
if __name__ == "__main__":
init_selected()

app = mk_app(
Expand All @@ -108,42 +107,42 @@ def init_selected():
get_members_from_instrument,
],
config={
APP_KEY: {'title': 'Data Binding'},
APP_KEY: {"title": "Data Binding"},
# OBJ_KEY: {'trans': crudify},
RENDERING_KEY: {
'get_instrument_from_band_and_member': {
NAME_KEY: 'From an input to another',
'description': {
'content': 'Select a band and see how the list of members is updated.'
"get_instrument_from_band_and_member": {
NAME_KEY: "From an input to another",
"description": {
"content": "Select a band and see how the list of members is updated."
},
'execution': {'on_submit': set_output_instrument},
"execution": {"on_submit": set_output_instrument},
},
'get_instrument_from_member_only': {
NAME_KEY: 'From a screen to another',
'description': {
'content': 'Select a band from the previous page ("From an input to another") and see how the list of members (of this page) is updated.'
"get_instrument_from_member_only": {
NAME_KEY: "From a screen to another",
"description": {
"content": 'Select a band from the previous page ("From an input to another") and see how the list of members (of this page) is updated.'
},
'execution': {'on_submit': set_output_instrument},
"execution": {"on_submit": set_output_instrument},
},
'get_members_from_instrument': {
NAME_KEY: 'From an output to an input',
'description': {
'content': 'Submit any other screen and see that the output is automatically selected from the "Instrument" select box.'
"get_members_from_instrument": {
NAME_KEY: "From an output to an input",
"description": {
"content": 'Submit any other screen and see that the output is automatically selected from the "Instrument" select box.'
},
},
Callable: {
'execution': {
'inputs': {
"execution": {
"inputs": {
str: {ELEMENT_KEY: SelectBox},
'band': {
'options': list(data),
'value': b.selected_band,
'on_value_change': on_select_band,
"band": {
"options": list(data),
"value": b.selected_band,
"on_value_change": on_select_band,
},
'member': {'options': b.members_of_selected_band},
'instrument': {
'options': instruments,
'value': b.output_instrument,
"member": {"options": b.members_of_selected_band},
"instrument": {
"options": instruments,
"value": b.output_instrument,
},
}
}
Expand Down
Loading
Loading