diff --git a/streamlitfront/elements/elements.py b/streamlitfront/elements/elements.py
index 392d310..1c71e6a 100644
--- a/streamlitfront/elements/elements.py
+++ b/streamlitfront/elements/elements.py
@@ -183,6 +183,9 @@ 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:
@@ -190,6 +193,17 @@ def _render_section_content(self):
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")
@@ -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):
diff --git a/streamlitfront/examples/crude.py b/streamlitfront/examples/crude.py
index 34fd95f..f461a44 100644
--- a/streamlitfront/examples/crude.py
+++ b/streamlitfront/examples/crude.py
@@ -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():
@@ -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': {
diff --git a/streamlitfront/examples/custom_output.py b/streamlitfront/examples/custom_output.py
index a503415..4ec8365 100644
--- a/streamlitfront/examples/custom_output.py
+++ b/streamlitfront/examples/custom_output.py
@@ -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..."))
@@ -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,
+ },
}
},
},
diff --git a/streamlitfront/examples/dag_app.py b/streamlitfront/examples/dag_app.py
index de352d3..d932b62 100644
--- a/streamlitfront/examples/dag_app.py
+++ b/streamlitfront/examples/dag_app.py
@@ -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):
@@ -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
@@ -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": ""},
}
},
},
diff --git a/streamlitfront/examples/data_binding.py b/streamlitfront/examples/data_binding.py
index b406340..e20951a 100644
--- a/streamlitfront/examples/data_binding.py
+++ b/streamlitfront/examples/data_binding.py
@@ -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",
},
}
@@ -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
@@ -98,7 +97,7 @@ def init_selected():
on_select_band(band)
-if __name__ == '__main__':
+if __name__ == "__main__":
init_selected()
app = mk_app(
@@ -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,
},
}
}
diff --git a/streamlitfront/examples/data_prep.py b/streamlitfront/examples/data_prep.py
index 1aed74b..f25e685 100644
--- a/streamlitfront/examples/data_prep.py
+++ b/streamlitfront/examples/data_prep.py
@@ -36,23 +36,23 @@
)
mall = b.mall()
if not b.selected_step_factory():
- b.selected_step_factory = 'files_of_folder'
+ b.selected_step_factory = "files_of_folder"
crudifier = partial(Crudifier, mall=mall)
-@crudifier(param_to_mall_map=dict(step_factory='step_factories'), output_store='steps')
+@crudifier(param_to_mall_map=dict(step_factory="step_factories"), output_store="steps")
def mk_step(step_factory: Callable, kwargs: dict):
return partial(step_factory, **kwargs)
-@crudifier(output_store='pipelines')
+@crudifier(output_store="pipelines")
def mk_pipeline(steps: Iterable[Callable]):
return Pipe(*steps)
@crudifier(
- param_to_mall_map=dict(pipeline='pipelines'),
+ param_to_mall_map=dict(pipeline="pipelines"),
# output_store='exec_outputs'
)
def exec_pipeline(pipeline: Callable, kwargs):
@@ -60,68 +60,68 @@ def exec_pipeline(pipeline: Callable, kwargs):
def get_step_name(step):
- return [k for k, v in mall['steps'].items() if v == step][0]
+ return [k for k, v in mall["steps"].items() if v == step][0]
def get_selected_pipeline_sig():
if not b.selected_pipeline():
return Sig()
- return Sig(mall['pipelines'][b.selected_pipeline()])
+ return Sig(mall["pipelines"][b.selected_pipeline()])
config = {
- APP_KEY: {'title': 'Data Preparation'},
+ APP_KEY: {"title": "Data Preparation"},
RENDERING_KEY: {
- 'mk_step': {
- NAME_KEY: 'Pipeline Step Maker',
- 'execution': {
- 'inputs': {
- 'step_factory': {
+ "mk_step": {
+ NAME_KEY: "Pipeline Step Maker",
+ "execution": {
+ "inputs": {
+ "step_factory": {
ELEMENT_KEY: SelectBox,
- 'options': mall['step_factories'],
- 'value': b.selected_step_factory,
+ "options": mall["step_factories"],
+ "value": b.selected_step_factory,
},
- 'kwargs': {
+ "kwargs": {
ELEMENT_KEY: KwargsInput,
- 'func_sig': Sig(
- mall['step_factories'][b.selected_step_factory()]
+ "func_sig": Sig(
+ mall["step_factories"][b.selected_step_factory()]
),
},
},
- 'output': {
+ "output": {
ELEMENT_KEY: SuccessNotification,
- 'message': 'The step has been created successfully.',
+ "message": "The step has been created successfully.",
},
},
},
- 'mk_pipeline': {
- NAME_KEY: 'Pipeline Maker',
- 'execution': {
- 'inputs': {
- 'steps': {
+ "mk_pipeline": {
+ NAME_KEY: "Pipeline Maker",
+ "execution": {
+ "inputs": {
+ "steps": {
ELEMENT_KEY: PipelineMaker,
- 'items': list(mall['steps'].values()),
- 'serializer': get_step_name,
+ "items": list(mall["steps"].values()),
+ "serializer": get_step_name,
},
},
- 'output': {
+ "output": {
ELEMENT_KEY: SuccessNotification,
- 'message': 'The pipeline has been created successfully.',
+ "message": "The pipeline has been created successfully.",
},
},
},
- 'exec_pipeline': {
- NAME_KEY: 'Pipeline Executor',
- 'execution': {
- 'inputs': {
- 'pipeline': {
+ "exec_pipeline": {
+ NAME_KEY: "Pipeline Executor",
+ "execution": {
+ "inputs": {
+ "pipeline": {
ELEMENT_KEY: SelectBox,
- 'options': mall['pipelines'],
- 'value': b.selected_pipeline,
+ "options": mall["pipelines"],
+ "value": b.selected_pipeline,
},
- 'kwargs': {
+ "kwargs": {
ELEMENT_KEY: KwargsInput,
- 'func_sig': get_selected_pipeline_sig(),
+ "func_sig": get_selected_pipeline_sig(),
},
}
},
@@ -129,7 +129,7 @@ def get_selected_pipeline_sig():
},
}
-if __name__ == '__main__':
+if __name__ == "__main__":
funcs = [mk_step, mk_pipeline, exec_pipeline]
app = mk_app(funcs, config=config)
app()
diff --git a/streamlitfront/examples/edge_impulse_like.py b/streamlitfront/examples/edge_impulse_like.py
index dd0941b..48b0c3f 100644
--- a/streamlitfront/examples/edge_impulse_like.py
+++ b/streamlitfront/examples/edge_impulse_like.py
@@ -42,21 +42,24 @@
# tagged_wf_store = appendable(Files, item2kv=tagged_timestamped_kv)
if not b.mall():
- b.mall = dict(tagged_wf=dict(), dummy_store=dict(),)
+ b.mall = dict(
+ tagged_wf=dict(),
+ dummy_store=dict(),
+ )
mall = b.mall()
crudifier = partial(Crudifier, mall=mall)
def auto_namer(*, arguments):
- return '_'.join(map(str, arguments.values()))
+ return "_".join(map(str, arguments.values()))
-@crudifier(output_store='tagged_wf', auto_namer=auto_namer)
+@crudifier(output_store="tagged_wf", auto_namer=auto_namer)
def tag_wf(wf: WaveForm, tag: str):
return (wf, tag)
-@crudifier(param_to_mall_map=dict(x='tagged_wf'))
+@crudifier(param_to_mall_map=dict(x="tagged_wf"))
def get_tagged_wf(x: Any):
return x
@@ -107,66 +110,76 @@ class TaggedAudioPlayer(OutputBase):
def render(self):
if self.output:
sound, tag = self.output
- arr = sf.read(BytesIO(sound), dtype='int16')[0]
- tab1, tab2 = st.tabs(['Audio Player', 'Waveform'])
+ arr = sf.read(BytesIO(sound), dtype="int16")[0]
+ tab1, tab2 = st.tabs(["Audio Player", "Waveform"])
with tab1:
st.audio(sound)
with tab2:
fig, ax = plt.subplots(figsize=(15, 5))
- ax.plot(arr, label=f'Tag={tag}')
+ ax.plot(arr, label=f"Tag={tag}")
ax.legend()
st.pyplot(fig)
# st.write(arr[:10])
-get_data_description = '''
+get_data_description = """
Feed the system with wave forms from wav files or directly \
from your microphone, and tag them.
-'''
+"""
config_ = {
- APP_KEY: {'title': 'Edge-Impulse-like App'},
+ APP_KEY: {"title": "Edge-Impulse-like App"},
RENDERING_KEY: {
- 'tag_wf': {
- NAME_KEY: 'Get Data',
- 'description': {'content': get_data_description},
- 'execution': {
- 'inputs': {
- 'wf': {
+ "tag_wf": {
+ NAME_KEY: "Get Data",
+ "description": {"content": get_data_description},
+ "execution": {
+ "inputs": {
+ "wf": {
ELEMENT_KEY: MultiSourceInput,
- NAME_KEY: 'Wave Form',
- 'From a file': {
+ NAME_KEY: "Wave Form",
+ "From a file": {
ELEMENT_KEY: FileReader,
- 'type': 'wav',
- 'display_label': False,
+ "type": "wav",
+ "display_label": False,
},
- 'From the microphone': {
+ "From the microphone": {
ELEMENT_KEY: AudioRecorder,
# "save_dir": rootdir,
},
}
},
- 'output': {
+ "output": {
ELEMENT_KEY: SuccessNotification,
- 'message': 'The wave form has been tagged successfully.',
+ "message": "The wave form has been tagged successfully.",
},
},
},
- 'get_tagged_wf': {
- NAME_KEY: 'Data Explorer',
- 'description': {
- 'content': '''Explore the existing tagged wave forms and play them.'''
+ "get_tagged_wf": {
+ NAME_KEY: "Data Explorer",
+ "description": {
+ "content": """Explore the existing tagged wave forms and play them."""
},
- 'execution': {
- 'output': {ELEMENT_KEY: TaggedAudioPlayer,},
- 'auto_submit': True,
+ "execution": {
+ "output": {
+ ELEMENT_KEY: TaggedAudioPlayer,
+ },
+ "auto_submit": True,
},
},
- DAG: {'graph': {ELEMENT_KEY: Graph, NAME_KEY: 'Flow',}},
+ DAG: {
+ "graph": {
+ ELEMENT_KEY: Graph,
+ NAME_KEY: "Flow",
+ }
+ },
Callable: {
- 'execution': {
- 'inputs': {
- 'save_name': {ELEMENT_KEY: TextInput, NAME_KEY: 'Save output as',}
+ "execution": {
+ "inputs": {
+ "save_name": {
+ ELEMENT_KEY: TextInput,
+ NAME_KEY: "Save output as",
+ }
}
}
},
@@ -174,6 +187,6 @@ def render(self):
}
# ============ END FRONTEND ============
-if __name__ == '__main__':
+if __name__ == "__main__":
app = mk_app(funcs, config=config_)
app()
diff --git a/streamlitfront/examples/experimentation.py b/streamlitfront/examples/experimentation.py
index d306018..a3b5001 100644
--- a/streamlitfront/examples/experimentation.py
+++ b/streamlitfront/examples/experimentation.py
@@ -8,20 +8,20 @@ def foo(a: int = 0, b: int = 0, c=0):
return (a * b) + c
-def bar(x, greeting='hello'):
+def bar(x, greeting="hello"):
"""bar greets its input"""
- return f'{greeting} {x}'
+ return f"{greeting} {x}"
def confuser(a: int = 0, x: float = 3.14):
- return (a ** 2) * x
+ return (a**2) * x
funcs = [foo, bar, confuser]
-if __name__ == '__main__':
+if __name__ == "__main__":
from streamlitfront.base import dispatch_funcs
- print(f'file: {os.path.realpath(__file__)}')
+ print(f"file: {os.path.realpath(__file__)}")
app = dispatch_funcs(funcs)
app()
diff --git a/streamlitfront/examples/handle_none_params.py b/streamlitfront/examples/handle_none_params.py
index f9362c2..500a1c7 100644
--- a/streamlitfront/examples/handle_none_params.py
+++ b/streamlitfront/examples/handle_none_params.py
@@ -15,18 +15,23 @@ def foo(a: int = 1, b: int | None = 2, c: int | None = None, d: str = None):
"""This is foo. It computes something"""
b = b if b is not None else 1
c = c if c is not None else 0
- d = d or ''
+ d = d or ""
return str((a * b) + c) + d
-def bar(x: str = 'World!', greeting: str = 'Hello'):
+def bar(x: str = "World!", greeting: str = "Hello"):
"""bar greets its input"""
- return f'{greeting} {x}'
+ return f"{greeting} {x}"
# ======
-if __name__ == '__main__':
- app = mk_app([foo, bar], config={APP_KEY: {'title': 'Handle None Parameters'},},)
+if __name__ == "__main__":
+ app = mk_app(
+ [foo, bar],
+ config={
+ APP_KEY: {"title": "Handle None Parameters"},
+ },
+ )
app()
diff --git a/streamlitfront/examples/issues.py b/streamlitfront/examples/issues.py
index 050d3c1..13f4218 100644
--- a/streamlitfront/examples/issues.py
+++ b/streamlitfront/examples/issues.py
@@ -24,8 +24,8 @@ def increment(self):
def display_selectbox(store):
- _keys = filt_iter(store, filt=lambda k: k.endswith('.csv'))
- st.selectbox('Choose a file', list(_keys))
+ _keys = filt_iter(store, filt=lambda k: k.endswith(".csv"))
+ st.selectbox("Choose a file", list(_keys))
def dacc(zip_path: type(lambda a: a), file: str): # file: list = []):
@@ -41,10 +41,10 @@ def foo(state: type(get_state()), word: str):
funcs = [my_incrementer, foo]
-if __name__ == '__main__':
+if __name__ == "__main__":
from streamlitfront.base import dispatch_funcs
- print(f'file: {os.path.realpath(__file__)}')
+ print(f"file: {os.path.realpath(__file__)}")
app = dispatch_funcs(funcs)
diff --git a/streamlitfront/examples/kaggle_front.py b/streamlitfront/examples/kaggle_front.py
index 5497e2a..6542a4c 100644
--- a/streamlitfront/examples/kaggle_front.py
+++ b/streamlitfront/examples/kaggle_front.py
@@ -17,19 +17,19 @@
# TODO: Resolve the UnhashableTypeError: Cannot hash object of type _thread.Rlock, found in something.
-DFLT_FEATURIZER = {'supervised': LdaChkToFv, 'unsupervised': PcaChkToFv}
+DFLT_FEATURIZER = {"supervised": LdaChkToFv, "unsupervised": PcaChkToFv}
def search_kaggle(
- keyword_search: str = '', num_to_display: int = 10, metadata: bool = False
+ keyword_search: str = "", num_to_display: int = 10, metadata: bool = False
):
s = KaggleDatasets()
results = s.search(keyword_search)
if metadata:
df = pd.DataFrame(results.values())[
- ['ref', 'title', 'subtitle', 'downloadCount', 'totalBytes']
+ ["ref", "title", "subtitle", "downloadCount", "totalBytes"]
]
- df = df.set_index('ref').sort_values('downloadCount', ascending=False)
+ df = df.set_index("ref").sort_values("downloadCount", ascending=False)
st.write(df.head(num_to_display))
else:
st.write(list(results)[:10])
@@ -51,30 +51,34 @@ def create_kaggle_dacc(
zip_path: type(lambda a: a),
annots_path: str,
wrangle_func: type(lambda a: a),
- extension: str = '.wav',
- key_path: str = '',
+ extension: str = ".wav",
+ key_path: str = "",
):
wrangle_func = dill.load(wrangle_func)
return mk_dacc(
- zip_path, annots_path, wrangle_func, extension=extension, key_path=key_path,
+ zip_path,
+ annots_path,
+ wrangle_func,
+ extension=extension,
+ key_path=key_path,
)
def build_and_run_model(tag: str, state: type(get_state())):
- dacc = state['Create Kaggle Dacc']
+ dacc = state["Create Kaggle Dacc"]
chks, tags = zip(*dacc.chk_tag_gen(tag))
scores, fvs, featurizer, model = produce_results(chks, tags)
return str(
- 'Percentage correct: '
+ "Percentage correct: "
+ str(sum(scores == tags) / len(tags))
- + ' vs. Random guessing: '
+ + " vs. Random guessing: "
+ str(1 / len(set(tags)))
)
def get_featurizer(chks, tags=None, **kwargs):
- proj_type = 'unsupervised' if (tags is None) else 'unsupervised'
- featurizer = mk_callable('transform')(DFLT_FEATURIZER[proj_type])(**kwargs)
+ proj_type = "unsupervised" if (tags is None) else "unsupervised"
+ featurizer = mk_callable("transform")(DFLT_FEATURIZER[proj_type])(**kwargs)
featurizer.fit(chks, tags)
return featurizer
@@ -87,7 +91,7 @@ def get_outlier_model(fvs, **kwargs):
def get_classification_model(fvs, tags, **kwargs):
- CS = mk_callable('predict')(CentroidSmoothing)
+ CS = mk_callable("predict")(CentroidSmoothing)
model = CentroidSmoothing(**kwargs)
return model.fit(fvs, tags)
@@ -122,12 +126,12 @@ def produce_results(
build_and_run_model,
]
-if __name__ == '__main__':
+if __name__ == "__main__":
from streamlitfront.base import dispatch_funcs
from streamlitfront.page_funcs import StatePageFunc
- print(f'file: {os.path.realpath(__file__)}')
+ print(f"file: {os.path.realpath(__file__)}")
- app = dispatch_funcs(funcs, configs={'page_factory': StatePageFunc})
+ app = dispatch_funcs(funcs, configs={"page_factory": StatePageFunc})
app()
diff --git a/streamlitfront/examples/mk_app.py b/streamlitfront/examples/mk_app.py
index 156de38..0c5c8e9 100644
--- a/streamlitfront/examples/mk_app.py
+++ b/streamlitfront/examples/mk_app.py
@@ -4,6 +4,7 @@
from streamlitfront.base import mk_app
from streamlitfront.elements.elements import TextInput
+
# BACKEND
def foo(a: int = 1, b: int = 2, c=3):
"""This is foo. It computes something"""
@@ -12,11 +13,11 @@ def foo(a: int = 1, b: int = 2, c=3):
def bar(x, greeting):
"""bar greets its input"""
- return f'{greeting} {x}'
+ return f"{greeting} {x}"
def confuser(a: int, x: float = 3.14):
- return (a ** 2) * x
+ return (a**2) * x
def proportion(x: int = 100, p: float = 0.5):
@@ -25,18 +26,24 @@ def proportion(x: int = 100, p: float = 0.5):
# ======
-if __name__ == '__main__':
+if __name__ == "__main__":
app = mk_app(
[foo, bar, confuser, proportion],
config={
- APP_KEY: {'title': 'My app'},
+ APP_KEY: {"title": "My app"},
RENDERING_KEY: {
# 'foo': {
# 'execution': {'inputs': {'a': {ELEMENT_KEY: TextInput,}},}
# },
- 'proportion': {
- 'execution': {'inputs': {'p': {ELEMENT_KEY: FloatSliderInput,}},}
+ "proportion": {
+ "execution": {
+ "inputs": {
+ "p": {
+ ELEMENT_KEY: FloatSliderInput,
+ }
+ },
+ }
}
},
},
diff --git a/streamlitfront/examples/pipeline_maker.py b/streamlitfront/examples/pipeline_maker.py
index b364913..77d2112 100644
--- a/streamlitfront/examples/pipeline_maker.py
+++ b/streamlitfront/examples/pipeline_maker.py
@@ -32,32 +32,32 @@ def __post_init__(self):
self.item_template = f.read()
def render(self):
- st_multiselect = mk_element_factory('st_multiselect')
+ st_multiselect = mk_element_factory("st_multiselect")
value = st_multiselect(options=self.options, item_template=self.item_template)
return value
-if __name__ == '__main__':
+if __name__ == "__main__":
app = mk_app(
[execute_pipe],
config={
- APP_KEY: {'title': 'Pipeline Maker'},
+ APP_KEY: {"title": "Pipeline Maker"},
RENDERING_KEY: {
Callable: {
- 'execution': {
- 'inputs': {
- 'pipe': {
+ "execution": {
+ "inputs": {
+ "pipe": {
ELEMENT_KEY: MultiSelect,
- 'options': [
- {'value': 'blue', 'name': 'Blue'},
- {'value': 'white', 'name': 'White'},
- {'value': 'red', 'name': 'Red'},
+ "options": [
+ {"value": "blue", "name": "Blue"},
+ {"value": "white", "name": "White"},
+ {"value": "red", "name": "Red"},
],
- 'item_template': '''
+ "item_template": """
{name} 😀
- ''',
+ """,
}
}
}
diff --git a/streamlitfront/examples/pipeline_maker_2.py b/streamlitfront/examples/pipeline_maker_2.py
index 86c02ca..2956781 100644
--- a/streamlitfront/examples/pipeline_maker_2.py
+++ b/streamlitfront/examples/pipeline_maker_2.py
@@ -12,27 +12,27 @@ def execute_pipe(pipe: Iterable):
return
-if __name__ == '__main__':
+if __name__ == "__main__":
app = mk_app(
[execute_pipe],
config={
- APP_KEY: {'title': 'Pipeline Maker'},
+ APP_KEY: {"title": "Pipeline Maker"},
RENDERING_KEY: {
Callable: {
- 'execution': {
- 'inputs': {
- 'pipe': {
+ "execution": {
+ "inputs": {
+ "pipe": {
ELEMENT_KEY: MultiSelect,
- 'options': [
- {'value': 'blue', 'name': 'Blue'},
- {'value': 'white', 'name': 'White'},
- {'value': 'red', 'name': 'Red'},
+ "options": [
+ {"value": "blue", "name": "Blue"},
+ {"value": "white", "name": "White"},
+ {"value": "red", "name": "Red"},
],
- 'item_template': '''
+ "item_template": """
{name} 😀
- ''',
+ """,
}
}
}
diff --git a/streamlitfront/examples/pos_key_args.py b/streamlitfront/examples/pos_key_args.py
index b8afeac..524b765 100644
--- a/streamlitfront/examples/pos_key_args.py
+++ b/streamlitfront/examples/pos_key_args.py
@@ -13,29 +13,29 @@ def add_floats(*nums_to_add: Iterable[float]):
def add_strs(*strs_to_add: Iterable[str]):
- return ' '.join(strs_to_add)
+ return " ".join(strs_to_add)
def add_bools(*bools_to_add: Iterable[bool]):
return sum(list(map(int, bools_to_add)))
-def make_a_table_of_ints(col_name: str = 'integers', **keys_and_values: dict[str, int]):
+def make_a_table_of_ints(col_name: str = "integers", **keys_and_values: dict[str, int]):
return pd.Series(data=keys_and_values, name=col_name)
def make_a_table_of_floats(
- col_name: str = 'floats', **keys_and_values: dict[str, float]
+ col_name: str = "floats", **keys_and_values: dict[str, float]
):
return pd.Series(data=keys_and_values, name=col_name)
-def make_a_table_of_strs(col_name: str = 'strings', **keys_and_values: dict[str, str]):
+def make_a_table_of_strs(col_name: str = "strings", **keys_and_values: dict[str, str]):
return pd.Series(data=keys_and_values, name=col_name)
def make_a_table_of_bools(
- col_name: str = 'booleans', **keys_and_values: dict[str, bool]
+ col_name: str = "booleans", **keys_and_values: dict[str, bool]
):
return pd.Series(data=keys_and_values, name=col_name)
@@ -51,12 +51,12 @@ def make_a_table_of_bools(
make_a_table_of_bools,
]
-if __name__ == '__main__':
+if __name__ == "__main__":
from streamlitfront.base import dispatch_funcs
from streamlitfront.page_funcs import ArgsPageFunc
- print(f'file: {os.path.realpath(__file__)}')
+ print(f"file: {os.path.realpath(__file__)}")
- app = dispatch_funcs(funcs, configs={'page_factory': ArgsPageFunc})
+ app = dispatch_funcs(funcs, configs={"page_factory": ArgsPageFunc})
app()
diff --git a/streamlitfront/examples/simple.py b/streamlitfront/examples/simple.py
index d306018..a3b5001 100644
--- a/streamlitfront/examples/simple.py
+++ b/streamlitfront/examples/simple.py
@@ -8,20 +8,20 @@ def foo(a: int = 0, b: int = 0, c=0):
return (a * b) + c
-def bar(x, greeting='hello'):
+def bar(x, greeting="hello"):
"""bar greets its input"""
- return f'{greeting} {x}'
+ return f"{greeting} {x}"
def confuser(a: int = 0, x: float = 3.14):
- return (a ** 2) * x
+ return (a**2) * x
funcs = [foo, bar, confuser]
-if __name__ == '__main__':
+if __name__ == "__main__":
from streamlitfront.base import dispatch_funcs
- print(f'file: {os.path.realpath(__file__)}')
+ print(f"file: {os.path.realpath(__file__)}")
app = dispatch_funcs(funcs)
app()
diff --git a/streamlitfront/examples/simple_real_audio_ml.py b/streamlitfront/examples/simple_real_audio_ml.py
index 0605891..10a927c 100644
--- a/streamlitfront/examples/simple_real_audio_ml.py
+++ b/streamlitfront/examples/simple_real_audio_ml.py
@@ -10,7 +10,6 @@
MultiSourceInput,
)
-
WaveForm = Iterable[int]
@@ -20,24 +19,30 @@ def learn_model(train_audio: WaveForm, raw_audio_learner):
config_ = {
- APP_KEY: {'title': 'Simple Real Audio ML'},
+ APP_KEY: {"title": "Simple Real Audio ML"},
RENDERING_KEY: {
DAG: {
- 'description': {'content': 'A very simple learn model example.'},
- 'execution': {
- 'inputs': {
- 'train_audio': {
+ "description": {"content": "A very simple learn model example."},
+ "execution": {
+ "inputs": {
+ "train_audio": {
ELEMENT_KEY: MultiSourceInput,
- 'From a file': {ELEMENT_KEY: FileUploader, 'type': 'wav',},
- 'From the microphone': {ELEMENT_KEY: AudioRecorder},
+ "From a file": {
+ ELEMENT_KEY: FileUploader,
+ "type": "wav",
+ },
+ "From the microphone": {ELEMENT_KEY: AudioRecorder},
}
},
},
- 'graph': {ELEMENT_KEY: Graph, NAME_KEY: 'Flow',},
+ "graph": {
+ ELEMENT_KEY: Graph,
+ NAME_KEY: "Flow",
+ },
}
},
}
-if __name__ == '__main__':
+if __name__ == "__main__":
app = mk_app([learn_model], config=config_)
app()
diff --git a/streamlitfront/examples/store_filtering.py b/streamlitfront/examples/store_filtering.py
index b1fc2e0..d35466b 100644
--- a/streamlitfront/examples/store_filtering.py
+++ b/streamlitfront/examples/store_filtering.py
@@ -10,7 +10,6 @@
from streamlitfront.base import mk_app
from streamlitfront.elements import TextOutput
-
# TODO: Being able to use Pipe to do pre and post processing in front elements
# TODO: have front (output) elements for iterables (example: paged iteration)
# TODO: Currying functionality in front
@@ -21,11 +20,11 @@ def filter_store(store: str, filt: str, flags: int = 0):
store_url = store
store = grab(store_url)
else:
- raise TypeError(f'store can only be str at this point: {store}')
+ raise TypeError(f"store can only be str at this point: {store}")
if isinstance(filt, str):
filt = re.compile(filt, flags).search
else:
- raise TypeError(f'filt can only be str at this point: {filt}')
+ raise TypeError(f"filt can only be str at this point: {filt}")
return filt_iter(store, filt=filt)
@@ -44,29 +43,29 @@ def list_keys(store_, max_keys: int = 100):
from itertools import islice
n = len(store_)
- first_keys = '\n\r'.join(islice(store_, 0, max_keys))
+ first_keys = "\n\r".join(islice(store_, 0, max_keys))
import pandas as pd
return pd.DataFrame(store_)
- return f'----- {n} keys. Below, the first {min(n, max_keys)} ----- \n\r{first_keys}'
+ return f"----- {n} keys. Below, the first {min(n, max_keys)} ----- \n\r{first_keys}"
from lined import LineParametrized
f = wrap(filter_store, egress=list_keys)
-f.__name__ = 'filter_store'
+f.__name__ = "filter_store"
-if __name__ == '__main__':
+if __name__ == "__main__":
app = mk_app(
# [filter_store],
[wrap(filter_store, egress=list_keys)],
# [LineParametrized(filter_store, list_keys)],
# [postprocess(list)(filter_store)],
config={
- APP_KEY: {'title': 'My app'},
+ APP_KEY: {"title": "My app"},
RENDERING_KEY: {
- 'filter_store': {
- 'execution': {'output': TextOutput},
+ "filter_store": {
+ "execution": {"output": TextOutput},
# 'execution': {'output': Pipe(list, TextOutput)},
},
},
diff --git a/streamlitfront/examples/tw_data_binding.py b/streamlitfront/examples/tw_data_binding.py
index 3643ce6..b09db42 100644
--- a/streamlitfront/examples/tw_data_binding.py
+++ b/streamlitfront/examples/tw_data_binding.py
@@ -6,7 +6,7 @@ def foo(text: str, integer: int, floating: float = 1.0):
return text, integer, floating
-def bar(a: str = 'boo'):
+def bar(a: str = "boo"):
"""About bar"""
return a
@@ -27,17 +27,17 @@ def pages_app(funcs, configs):
state = get_state(hash_funcs=dflt_hash_funcs) # TODO: get from configs
# Page setup
- st.set_page_config(layout='wide')
+ st.set_page_config(layout="wide")
# Make page objects
views = get_pages_specs(funcs, **configs)
- state['views'] = views
+ state["views"] = views
# TODO: The above is static: Should the above be done only once, and cached?
# Perhaps views should be cached in state?
# Setup navigation
- view_key = _get_view_key(tuple(views.keys()), label='Select your view')
+ view_key = _get_view_key(tuple(views.keys()), label="Select your view")
# Display the selected page with the session state
# This is the part that actually runs the functionality that pages specifies
@@ -48,11 +48,11 @@ def pages_app(funcs, configs):
state.sync()
-if __name__ == '__main__':
+if __name__ == "__main__":
from streamlitfront.base import dispatch_funcs
from streamlitfront.page_funcs import ExperimentalViewFunc
- print(f'file: {os.path.realpath(__file__)}')
+ print(f"file: {os.path.realpath(__file__)}")
app = dispatch_funcs(funcs, configs=dict(page_factory=ExperimentalViewFunc))
diff --git a/streamlitfront/examples/util.py b/streamlitfront/examples/util.py
index 45c14e9..80c9931 100644
--- a/streamlitfront/examples/util.py
+++ b/streamlitfront/examples/util.py
@@ -27,8 +27,8 @@ def get_code_of_current_file():
SourceCodeSection = partial(
TextSection,
- name='Source Code',
- kind='code',
- language='python',
+ name="Source Code",
+ kind="code",
+ language="python",
content=get_code_of_current_file(),
)
diff --git a/streamlitfront/examples/various_input_kinds.py b/streamlitfront/examples/various_input_kinds.py
index 7e2650c..3944dc9 100644
--- a/streamlitfront/examples/various_input_kinds.py
+++ b/streamlitfront/examples/various_input_kinds.py
@@ -10,20 +10,20 @@ def foo(a: int = 0, b: int = 0, c=0):
class Klass:
- def __init__(self, greeting='hello', confuser_x: float = 3.14):
+ def __init__(self, greeting="hello", confuser_x: float = 3.14):
self.greeting = greeting
self.confuser_x = confuser_x
def bar(self, x):
"""bar greets its input"""
- return f'{self.greeting} {x}'
+ return f"{self.greeting} {x}"
# def konfuser(self, a: int = 0):
# return (a ** 2) * self.confuser_x
def i_am_confused(self, a: int = 0):
- return (a ** 2) * self.confuser_x
+ return (a**2) * self.confuser_x
klass_instance = Klass()
@@ -34,9 +34,9 @@ def i_am_confused(self, a: int = 0):
funcs = [foo, klass_instance.bar, klass_instance.konfuser]
-if __name__ == '__main__':
+if __name__ == "__main__":
from streamlitfront.base import dispatch_funcs
- print(f'file: {os.path.realpath(__file__)}')
+ print(f"file: {os.path.realpath(__file__)}")
app = dispatch_funcs(funcs)
app()
diff --git a/streamlitfront/page_funcs.py b/streamlitfront/page_funcs.py
index 6fec3ac..1142bf9 100644
--- a/streamlitfront/page_funcs.py
+++ b/streamlitfront/page_funcs.py
@@ -17,7 +17,6 @@
signature_defaults,
)
-
# TODO: Extract the page setup (view_title, etc.) and make it injectable.
{int: st.number_input, float: st.number_input, str: st.text_input, bool: st.checkbox}
@@ -206,9 +205,9 @@ def __call__(self, state):
else:
if "options" in args_specs[argname]["element_factory"][1]:
options = func_inputs[int_args[idx - 1]].split(", ")
- args_specs[argname]["element_factory"][1]["options"] = (
- options
- )
+ args_specs[argname]["element_factory"][1][
+ "options"
+ ] = options
element_factory, kwargs = args_specs[argname]["element_factory"]
func_inputs[argname] = element_factory(**kwargs)
submit = st.button("Submit")
@@ -302,7 +301,9 @@ def __call__(self, state):
mytype = self.func.__annotations__["return"]
output_model = pydantic_model_from_type(mytype)
- name = self.func.__name__ # check in sig, dag, lined a better way, i2, may be displayed name: name_of_obj
+ name = (
+ self.func.__name__
+ ) # check in sig, dag, lined a better way, i2, may be displayed name: name_of_obj
data = sp.pydantic_input(key=f"my_form_{name}", model=mymodel)
diff --git a/streamlitfront/scrap/alt_renderer.py b/streamlitfront/scrap/alt_renderer.py
index 316a7a9..517a92b 100644
--- a/streamlitfront/scrap/alt_renderer.py
+++ b/streamlitfront/scrap/alt_renderer.py
@@ -81,12 +81,11 @@ class StreamlitRenderers:
import streamlit as st
from importlib_resources import files # importlib.resources once only 3.9+ maintained
-
-data_files = files('streamlitfront').joinpath('data')
+data_files = files("streamlitfront").joinpath("data")
streamlit_element_func_names = list(
- filter(None, data_files.joinpath('streamlit_elements.txt').read_text().split('\n'))
+ filter(None, data_files.joinpath("streamlit_elements.txt").read_text().split("\n"))
)
@@ -97,7 +96,7 @@ class StreamlitRenderers:
StreamlitRenderers, name, staticmethod(mk_renderer_factory_for_func(func))
)
else:
- print(f'Missing {name}')
+ print(f"Missing {name}")
# TODO: Add warning if func missing?
@@ -126,20 +125,20 @@ def mk_component_base(
name: str = None,
) -> type:
# TODO: could "camelize" name_of_obj output.
- name = name or name_of_obj(component_factory) or 'ComponentBase'
+ name = name or name_of_obj(component_factory) or "ComponentBase"
component_sig = Sig(component_factory)
- sig: Sig = 'self' + component_sig + Sig(base_cls)
+ sig: Sig = "self" + component_sig + Sig(base_cls)
@sig
def __init__(*args, **kwargs) -> None:
kw = sig.map_arguments(args, kwargs, apply_defaults=True)
- self = kw.pop('self')
+ self = kw.pop("self")
call_forgivingly(base_cls.__init__, self, **kw)
for name in component_sig:
if not hasattr(self, name):
setattr(self, name, kw[name])
- return type(name, (base_cls,), {'__init__': __init__, 'render': render})
+ return type(name, (base_cls,), {"__init__": __init__, "render": render})
def implement_component_from_factory(
@@ -173,8 +172,8 @@ def test_mk_component_base():
# InputBase:
assert sorted(Sig(C).names) == sorted((Sig(st.audio) + Sig(InputBase)).names)
- c = C(1, name='tag')
- assert (c.data, c.name) == (1, 'tag')
+ c = C(1, name="tag")
+ assert (c.data, c.name) == (1, "tag")
def test_implement_component_from_factory():
diff --git a/streamlitfront/scrap/front_routing_proposals.py b/streamlitfront/scrap/front_routing_proposals.py
index 5bd1aee..1a3f967 100644
--- a/streamlitfront/scrap/front_routing_proposals.py
+++ b/streamlitfront/scrap/front_routing_proposals.py
@@ -1,6 +1,4 @@
-"""Tools made with streamlitfront
-
-"""
+"""Tools made with streamlitfront"""
from typing import Tuple, KT, VT
from collections.abc import Iterable, Callable
@@ -26,9 +24,9 @@ def first_element_matching_type(obj, types):
# else return None
-OBJECT = 'OBJECT'
-NAME_OF_OBJ = 'NAME_OF_OBJ'
-SUBTYPE = 'SUBTYPE'
+OBJECT = "OBJECT"
+NAME_OF_OBJ = "NAME_OF_OBJ"
+SUBTYPE = "SUBTYPE"
# TODO: Finish. Intened to be used with _find_render_keys function that handles a
@@ -58,6 +56,7 @@ def first_element_matching_type(obj, types):
# f"Object couldn't be mapped to a render_keys key: {obj}"
# )
+
# TODO: Extract routing logic (the if/elifs) and expose control to interface
def _find_render_keys(objs, render_keys: dict):
"""Make a static configs that has a one to one relationship with objects"""
@@ -147,8 +146,8 @@ def alt_mk_render_keys(objs, render_keys, resolver: Callable):
from typing import TypeVar, Tuple
from collections.abc import Iterable, Callable
-Obj = TypeVar('Obj')
-Output = TypeVar('Output')
+Obj = TypeVar("Obj")
+Output = TypeVar("Output")
Cond = Callable[[Obj], bool]
Then = Callable[[Obj], Output]
Rule = tuple[Cond, Then]
@@ -168,6 +167,7 @@ def alt_mk_render_keys(objs, render_keys, resolver: Callable):
# -----------------------------------------------------------------------------
# Say we want to make a function like the following have a plugin architecture
+
# TODO: Extract routing logic (the if/elifs) and expose control to interface
def target_render_keys_func(obj, render_keys):
if obj in render_keys:
@@ -292,9 +292,7 @@ def if_feature_then_feature_and_obj(feature, cond, then, obj):
def rule_applier(obj, rules, render_keys, sentinel=None):
- """Applies rules to obj and returns the first non-sentinel value
-
- """
+ """Applies rules to obj and returns the first non-sentinel value"""
for rule in rules:
if (result := rule(obj, render_keys)) is not sentinel:
return result
diff --git a/streamlitfront/scrap/nested_select.py b/streamlitfront/scrap/nested_select.py
index 32a6085..495ac37 100644
--- a/streamlitfront/scrap/nested_select.py
+++ b/streamlitfront/scrap/nested_select.py
@@ -31,29 +31,29 @@ def render(self):
# level_1 = st_multiselect(options=self.options)
# value = st_multiselect(options=self.options[level_1])
# return value
- with st.form('first_form2'):
- level_1 = st.selectbox(label='l1', options=self.options)
- change = st.form_submit_button('Select')
- level_2 = st.selectbox(label='l2', options=self.options[level_1])
- submit = st.form_submit_button('Submit')
+ with st.form("first_form2"):
+ level_1 = st.selectbox(label="l1", options=self.options)
+ change = st.form_submit_button("Select")
+ level_2 = st.selectbox(label="l2", options=self.options[level_1])
+ submit = st.form_submit_button("Submit")
value = self.options[level_1][level_2]
return value
-if __name__ == '__main__':
+if __name__ == "__main__":
app = mk_app(
[dummy],
config={
- APP_KEY: {'title': 'Nested Chooser'},
+ APP_KEY: {"title": "Nested Chooser"},
RENDERING_KEY: {
Callable: {
- 'execution': {
- 'inputs': {
- 'my_dict': {
+ "execution": {
+ "inputs": {
+ "my_dict": {
ELEMENT_KEY: NestedSelect,
- 'options': {
- 'level_0_a': {'level_1_a': 'aa', 'level_2': 'bb'},
- 'level_0_b': {'level_1_b': 'cc', 'level_2': 'dd'},
+ "options": {
+ "level_0_a": {"level_1_a": "aa", "level_2": "bb"},
+ "level_0_b": {"level_1_b": "cc", "level_2": "dd"},
},
}
}
diff --git a/streamlitfront/spec_maker.py b/streamlitfront/spec_maker.py
index f842ffe..70a0191 100644
--- a/streamlitfront/spec_maker.py
+++ b/streamlitfront/spec_maker.py
@@ -27,7 +27,6 @@
)
from streamlitfront.data_binding import BoundData
-
DFLT_CONVENTION_DICT = deep_merge(
BASE_DFLT_CONVENTION,
{
diff --git a/streamlitfront/tests/common.py b/streamlitfront/tests/common.py
index 48d562a..4ad01ba 100644
--- a/streamlitfront/tests/common.py
+++ b/streamlitfront/tests/common.py
@@ -19,7 +19,7 @@
import dill
import pickle
-STREAMLIT_APP_URL = 'http://localhost:8501'
+STREAMLIT_APP_URL = "http://localhost:8501"
@contextmanager
@@ -34,12 +34,12 @@ def dispatch_funcs_with_selenium(funcs, headless=False):
except:
serialize_funcs = True
_funcs = dill.dumps(funcs) if serialize_funcs else funcs
- with run_process(func=run_app, func_kwargs={'funcs': _funcs}, is_ready=3) as proc:
+ with run_process(func=run_app, func_kwargs={"funcs": _funcs}, is_ready=3) as proc:
options = ChromeOptions()
# options.add_argument('--no-sandbox')
- options.add_argument('--window-size=1920,1080')
+ options.add_argument("--window-size=1920,1080")
if headless:
- options.add_argument('--headless')
+ options.add_argument("--headless")
# options.add_argument('--disable-gpu')
# options.add_argument('--allow-running-insecure-content')
dom = Chrome(service=Service(ChromeDriverManager().install()), options=options)
@@ -55,6 +55,7 @@ def give_a_chance_to_render_element(func):
Gives a chance to the application to render the element by trying up to three times
with 1 second of interval to find it before raising an error.
"""
+
# @wrap(func)
def wrapper(*args, **kwargs):
def _try_to_find_element(intent_nb):
@@ -88,9 +89,9 @@ def select_func(idx, root):
def send_input(input_, idx, root):
def get_input_type():
if isinstance(input_, Number):
- return 'number'
+ return "number"
if isinstance(input_, str):
- return 'text'
+ return "text"
input_type = get_input_type()
input_el = find_element_by_css_selector(
@@ -98,16 +99,16 @@ def get_input_type():
root,
)
input_el.click()
- select_all_first_key = Keys.COMMAND if platform == 'darwin' else Keys.CONTROL
- input_el.send_keys(select_all_first_key, 'a')
+ select_all_first_key = Keys.COMMAND if platform == "darwin" else Keys.CONTROL
+ input_el.send_keys(select_all_first_key, "a")
input_el.send_keys(str(input_))
def compute_output(func, root):
def get_output(previous_output=None, intent_nb=1):
output_el = find_element_by_css_selector(output_css_selector, root)
- if output_el.find_elements(By.TAG_NAME, 'code'):
- output_el = find_element_by_css_selector('code', output_el)
+ if output_el.find_elements(By.TAG_NAME, "code"):
+ output_el = find_element_by_css_selector("code", output_el)
output = output_el.text
return_annot = Sig(func).return_annotation
if return_annot not in (Parameter.empty, Any):
@@ -122,10 +123,10 @@ def get_previous_output():
return get_output()
nb_args = len(Sig(func))
- output_css_selector = f'.element-container:nth-child({nb_args + 3}) .stMarkdown p'
+ output_css_selector = f".element-container:nth-child({nb_args + 3}) .stMarkdown p"
previous_output = get_previous_output()
submit_button = find_element_by_css_selector(
- f'.element-container:nth-child({nb_args + 2}) button', root
+ f".element-container:nth-child({nb_args + 2}) button", root
)
submit_button.click()
return get_output(previous_output)
diff --git a/streamlitfront/tests/conftest.py b/streamlitfront/tests/conftest.py
index 0a73d0c..7dccf25 100644
--- a/streamlitfront/tests/conftest.py
+++ b/streamlitfront/tests/conftest.py
@@ -1,8 +1,8 @@
def pytest_addoption(parser):
- parser.addoption('--headless', action='store_true', default=False)
+ parser.addoption("--headless", action="store_true", default=False)
def pytest_generate_tests(metafunc):
option_value = metafunc.config.option.headless
- if 'headless' in metafunc.fixturenames and option_value is not None:
- metafunc.parametrize('headless', [option_value])
+ if "headless" in metafunc.fixturenames and option_value is not None:
+ metafunc.parametrize("headless", [option_value])
diff --git a/streamlitfront/tests/dummy_app.py b/streamlitfront/tests/dummy_app.py
index ab35ff4..222107e 100644
--- a/streamlitfront/tests/dummy_app.py
+++ b/streamlitfront/tests/dummy_app.py
@@ -23,10 +23,10 @@ class SimplePageFunc2(BasePageFunc):
def __call__(self, state):
self.prepare_view(state)
# args_specs = get_func_args_specs(self.func)
- element = sp.pydantic_input('input', Input)
+ element = sp.pydantic_input("input", Input)
st.write(element)
# func_inputs = dict(self.sig.defaults, **state['page_state'][self.func])
- func_inputs = {'input': element}
+ func_inputs = {"input": element}
st.write(func_inputs)
# for argname, spec in args_specs.items():
# st.write(f"argname:{argname}")
@@ -36,15 +36,15 @@ def __call__(self, state):
# st.write(f"element_factory:{element_factory}")
# st.write(f"kwargs:{kwargs}")
- submit = st.button('Submit')
+ submit = st.button("Submit")
if submit:
- st.write(self.func(func_inputs['input']))
+ st.write(self.func(func_inputs["input"]))
# state['page_state'][self.func].clear()
DFLT_PAGE_FACTORY = SimplePageFunc2
-if __name__ == '__main__':
+if __name__ == "__main__":
app = get_pages_specs([multiple_input], page_factory=DFLT_PAGE_FACTORY)
- app['Multiple Input'](None)
+ app["Multiple Input"](None)
diff --git a/streamlitfront/tests/fake_app.py b/streamlitfront/tests/fake_app.py
index 3082e83..16d8490 100644
--- a/streamlitfront/tests/fake_app.py
+++ b/streamlitfront/tests/fake_app.py
@@ -1,3 +1,3 @@
import streamlit as st
-st.write('fake app!')
+st.write("fake app!")
diff --git a/streamlitfront/tests/test_base.py b/streamlitfront/tests/test_base.py
index c70ce16..3cff850 100644
--- a/streamlitfront/tests/test_base.py
+++ b/streamlitfront/tests/test_base.py
@@ -8,5 +8,5 @@ def double_func(x: int) -> int:
def test_func_to_page_name():
result = func_to_page_name(double_func)
- expected = 'Double Func'
+ expected = "Double Func"
assert result == expected
diff --git a/streamlitfront/tests/test_pydantic_widgets.py b/streamlitfront/tests/test_pydantic_widgets.py
index e248786..0a9298d 100644
--- a/streamlitfront/tests/test_pydantic_widgets.py
+++ b/streamlitfront/tests/test_pydantic_widgets.py
@@ -11,7 +11,6 @@
from streamlitfront.pydantic_widgets import _unwrap_optional
-
# ----------------------------------------------------------------------------
# Pure-helper unit tests
diff --git a/streamlitfront/util.py b/streamlitfront/util.py
index 57c1381..d3e64fd 100644
--- a/streamlitfront/util.py
+++ b/streamlitfront/util.py
@@ -76,7 +76,8 @@ def mk_next_str():
def func_name(func):
"""The func.__name__ of a callable func, or makes and returns one if that fails.
- To make one, it calls unamed_func_name which produces incremental names to reduce the chances of clashing"""
+ To make one, it calls unamed_func_name which produces incremental names to reduce the chances of clashing
+ """
name = name_of_obj(func)
if name is None or name == "":
return unnamed_page()
diff --git a/tests/test_file_uploader_clear.py b/tests/test_file_uploader_clear.py
new file mode 100644
index 0000000..15b6ef1
--- /dev/null
+++ b/tests/test_file_uploader_clear.py
@@ -0,0 +1,95 @@
+"""A file uploader is emptied after a successful submit, so the next upload doesn't start
+from the previous selection (i2mint/front#24). Runs the app headlessly with ``AppTest``.
+"""
+
+import pytest
+
+pytest.importorskip("streamlit.testing.v1")
+from streamlit.testing.v1 import AppTest
+
+APP = """
+import streamlit as st
+from front import APP_KEY, RENDERING_KEY, ELEMENT_KEY
+from streamlitfront import mk_app
+from streamlitfront.elements import FileUploader, MultiSourceInput, TextOutput
+
+def count_bytes(data: list) -> int:
+ if not data:
+ raise ValueError("no data")
+ st.info("drawn by the function")
+ return sum(len(f.getvalue()) for f in data)
+
+class Files(FileUploader):
+ def render(self):
+ return super().render() or []
+
+data_spec = {ELEMENT_KEY: Files, "accept_multiple_files": True, **EXTRA}
+if MULTI:
+ data_spec = {ELEMENT_KEY: MultiSourceInput, "From files": data_spec}
+
+mk_app(
+ [count_bytes],
+ config={
+ APP_KEY: {"title": "t"},
+ RENDERING_KEY: {
+ "count_bytes": {
+ "execution": {
+ "inputs": {"data": data_spec},
+ "output": {ELEMENT_KEY: TextOutput},
+ }
+ }
+ },
+ },
+)()
+"""
+
+
+def _run_app(*, multi=False, **uploader_spec):
+ script = APP.replace("EXTRA", repr(uploader_spec)).replace("MULTI", repr(multi))
+ at = AppTest.from_string(script, default_timeout=30).run()
+ assert not at.exception
+ return at
+
+
+def _names(at):
+ return [f.name for f in at.file_uploader[0].value]
+
+
+def _output(at):
+ return [m.value for m in at.markdown if m.value.startswith("`")]
+
+
+@pytest.mark.parametrize("multi", [False, True])
+def test_uploader_is_emptied_after_submit_and_output_is_kept(multi):
+ at = _run_app(multi=multi)
+ at.file_uploader[0].upload("a.txt", b"12345").run()
+ assert _names(at) == ["a.txt"]
+ at.button[0].click().run()
+ assert not at.exception
+ assert _names(at) == []
+ assert len(at.file_uploader) == 1 # replaced, not added
+ assert _output(at) == ["`5`"]
+ assert [i.value for i in at.info] == [
+ "drawn by the function"
+ ] # nothing redrawn away
+ # a new upload starts from a clean selection; the stale output goes away as before
+ at.file_uploader[0].upload("b.txt", b"67").run()
+ assert _names(at) == ["b.txt"]
+ assert _output(at) == []
+
+
+def test_clear_after_submit_false_keeps_the_selection():
+ at = _run_app(clear_after_submit=False)
+ at.file_uploader[0].upload("a.txt", b"12345").run()
+ at.button[0].click().run()
+ assert _names(at) == ["a.txt"]
+ assert _output(at) == ["`5`"]
+
+
+def test_failed_submit_keeps_the_selection():
+ """The function raises on empty data; nothing was submitted, so nothing is cleared."""
+ at = _run_app()
+ at.button[0].click().run()
+ assert at.exception # ValueError from the function propagates, as before
+ at.file_uploader[0].upload("a.txt", b"").run()
+ assert _names(at) == ["a.txt"]