diff --git a/CHANGELOG.md b/CHANGELOG.md index e449a3a..006639c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 Release dates are in YYYY-MM-DD +## [0.7.0] - 2022-08-30 + +- Add Block state to store data private to the block. Using block.get_state('key'), block.set_state('key'). +- Block state also stores execution status and errors. Using block.get_state('info'), block.set_state('info'). Note: 'info' is a reserved key. +- When a block fails on its compute function, its descendants are skipped and the rest of the blocks are computed. +- Add delete schema function. (@zabrewer) + ## [0.6.1] - 2022-07-25 - Fix base_blocks_list passed to the compute engine. diff --git a/Developer.md b/Developer.md index 4d14bd0..96905f7 100644 --- a/Developer.md +++ b/Developer.md @@ -6,13 +6,12 @@ Following are the notes for working on the development of the barfi. - In terminal 1 ```shell -$ cd st_barfi/frontend -$ npm run serve +make serve ``` - In terminal 2 ```shell -$ streamlit run st_barfi/__init__.py +make run ``` ## Requirements @@ -34,7 +33,7 @@ Run the components's Streamlit app: ```shell $ . venv/bin/activate # activate the venv you created earlier -$ streamlit run st_barfi/__init__.py # run the root test +$ streamlit run barfi/__init__.py # run the root test ``` ## Node environment @@ -42,7 +41,7 @@ $ streamlit run st_barfi/__init__.py # run the root test Install and initialize the component's frontend: ```shell -$ cd st_barfi/frontend +$ cd barfi/frontend $ npm install # Install npm dependencies $ npm run serve # Start the dev server ``` diff --git a/barfi/block_builder.py b/barfi/block_builder.py index b777df7..b6a08fb 100644 --- a/barfi/block_builder.py +++ b/barfi/block_builder.py @@ -1,5 +1,5 @@ import types -from typing import Callable +from typing import Callable, Any from .option_builder import build_option @@ -18,12 +18,13 @@ def __init__(self, name: str = 'Block') -> None: self._inputs = {} self._outputs = {} self._options = {} + self._state = {'info': None} self._interface_names = [] def __repr__(self) -> str: return f'' - def __str__(self) -> str: + def __str__(self) -> str: inputs_name = [input['name'] for input in self._inputs] outputs_name = [output['name'] for output in self._outputs] options_name = [option['name'] for option in self._options] @@ -33,7 +34,7 @@ def __str__(self) -> str: line_4 = f'Options: {options_name!r} ' return line_1 + line_2 + line_3 + line_4 - def add_input(self, name: str = None, value = None) -> None: + def add_input(self, name: str = None, value=None) -> None: """ A function defined to add an Input interface to the Block @@ -43,18 +44,20 @@ def add_input(self, name: str = None, value = None) -> None: Interface options: name (str) : The name of the Input interface. value (any) : The default value for this input interface. - """ + """ if name: - if name in self._interface_names: raise ValueError(f'name: {name} already exists as an interface to the Block.') + if name in self._interface_names: + raise ValueError( + f'name: {name} already exists as an interface to the Block.') self._inputs[name] = {'value': value, 'id': None} self._interface_names.append(name) else: in_nos = len(self._inputs) name = 'Input ' + str(in_nos + 1) - self._inputs[name] = {'value': value, 'id': None} - self._interface_names.append(name) + self._inputs[name] = {'value': value, 'id': None} + self._interface_names.append(name) - def add_output(self, name: str = None, value = None) -> None: + def add_output(self, name: str = None, value=None) -> None: """ A function defined to add an Output interface to the Block @@ -66,9 +69,11 @@ def add_output(self, name: str = None, value = None) -> None: value (any) : The default value for this output interface. """ if name: - if name in self._interface_names: raise ValueError(f'name: {name} already exists as an interface to the Block.') + if name in self._interface_names: + raise ValueError( + f'name: {name} already exists as an interface to the Block.') self._outputs[name] = {'value': value, 'id': None} - self._interface_names.append(name) + self._interface_names.append(name) else: out_nos = len(self._outputs) name = 'Output ' + str(out_nos + 1) @@ -76,30 +81,43 @@ def add_output(self, name: str = None, value = None) -> None: self._interface_names.append(name) def get_interface(self, name: str): - + if name in self._inputs: - return self._inputs[name]['value'] + return self._inputs[name]['value'] elif name in self._outputs: - return self._outputs[name]['value'] + return self._outputs[name]['value'] else: - raise ValueError(f'No interface with name: {name} found for Block') - return None + raise ValueError(f'No interface with name: {name} found for Block') def set_interface(self, name: str, value) -> None: if name in self._inputs: - self._inputs[name]['value'] = value + self._inputs[name]['value'] = value elif name in self._outputs: self._outputs[name]['value'] = value else: - raise ValueError(f'No interface with name: {name} found for Block') + raise ValueError(f'No interface with name: {name} found for Block') def _set_interface_id(self, name: str, id: str) -> None: if name in self._inputs: self._inputs[name]['id'] = id - elif name in self._outputs: + elif name in self._outputs: self._outputs[name]['id'] = id else: - raise ValueError(f'No interface with name: {name} found for Block') + raise ValueError(f'No interface with name: {name} found for Block') + + def set_state(self, key: str, value: Any) -> None: + reserved_state_keys = ['info'] + if key in reserved_state_keys: + raise ValueError( + f'Key: {key} used for setting state of block is reserved. Use another key.') + else: + self._state[key] = value + + def get_state(self, key: str) -> Any: + if key in self._state: + return self._state[key] + else: + raise ValueError(f'Key: {key} does not exist in state.') def add_option(self, name: str, type: str, **kwargs) -> None: """ @@ -125,30 +143,33 @@ def add_option(self, name: str, type: str, **kwargs) -> None: 'display'], 'Error: Option "type" is not of standard Option interface parameter.' if name in self._options: - raise ValueError(f'Option with name: {name} aready exists in Block.') + raise ValueError( + f'Option with name: {name} aready exists in Block.') _option = build_option(name, type, kwargs) self._options[_option['name']] = _option - def set_option(self, name: str, **kwargs): - # Can only set the 'value' property for now. + def set_option(self, name: str, **kwargs): + # Can only set the 'value' property for now. if name in self._options: for arg, value in kwargs.items(): if arg in self._options[name]: if arg in ['value']: self._options[name][arg] = value else: - raise ValueError(f'Cannot set or invalid property: {arg} for Block option.') + raise ValueError( + f'Cannot set or invalid property: {arg} for Block option.') else: - raise ValueError(f'Property: {arg} is not a valid option property for {name}.') - else: + raise ValueError( + f'Property: {arg} is not a valid option property for {name}.') + else: raise ValueError(f'Option name: {name} does not exist in Block.') - def get_option(self, name: str): + def get_option(self, name: str): if name in self._options: return self._options[name]['value'] - else: + else: raise ValueError(f'Option name: {name} does not exist in Block.') def _export(self): diff --git a/barfi/compute_engine.py b/barfi/compute_engine.py index 2779c49..a8e1021 100644 --- a/barfi/compute_engine.py +++ b/barfi/compute_engine.py @@ -62,10 +62,10 @@ def _map_block_link(self): ['id']] = _interface[0] _child_block._set_interface_id( name=_interface[0], id=_interface[1]['id']) - _block_interfaces[_interface[0]] = _interface[1] + _block_interfaces[_interface[0]] = _interface[1] - for _option in _block['options']: - _child_block.set_option(name=_option[0], value=_option[1]) + for _option in _block['options']: + _child_block.set_option(name=_option[0], value=_option[1]) # Active blocks build from the base-blocks and editor-state self._active_blocks[_block['id']] = { @@ -131,14 +131,35 @@ def _map_block_link(self): def _execute_compute(self): if bool(self._editor_state): - + skip_node = set() + descendant_set = {} for node in nx.topological_sort(self._graph): - self._active_blocks[node]['block']._on_compute() - for key, value in self._active_blocks[node]['block']._outputs.items(): + if node not in skip_node: try: - for find_to in self._map_link_interface_id_from_to[value['id']]: - find_to_block = self._map_interface_id_block_id[find_to] - self._active_blocks[find_to_block]['block'].set_interface( - name=self._map_interface_id_name[find_to], value=value['value']) - except: - pass + self._active_blocks[node]['block']._on_compute() + self._active_blocks[node]['block']._state['info'] = { + 'status': 'Computed'} + + for _, value in self._active_blocks[node]['block']._outputs.items(): + try: + for find_to in self._map_link_interface_id_from_to[value['id']]: + find_to_block = self._map_interface_id_block_id[find_to] + self._active_blocks[find_to_block]['block'].set_interface( + name=self._map_interface_id_name[find_to], value=value['value']) + except: + pass + + except Exception as e: + self._active_blocks[node]['block']._state['info'] = { + 'status': 'Errored', 'exception': e.args} + node_desc = nx.descendants(self._graph, node) + descendant_set[self._active_blocks[node] + ['name']] = node_desc + skip_node = skip_node.union(node_desc) + + else: + for parent, child_set in descendant_set.items(): + if node in child_set: + break + self._active_blocks[node]['block']._state['info'] = { + 'status': 'Errored', 'message': 'Parent block errored', 'parent': parent} diff --git a/barfi/manage_schema.py b/barfi/manage_schema.py index 5d95eae..cfe91de 100644 --- a/barfi/manage_schema.py +++ b/barfi/manage_schema.py @@ -37,3 +37,20 @@ def load_schema_name(schema_name: str) -> Dict: else: raise ValueError( f'Schema :{schema_name}: not found in the saved schemas') + + +def delete_schema(schema_name: str): + try: + with open('schemas.barfi', 'rb') as handle_read: + schemas = pickle.load(handle_read) + except FileNotFoundError: + schemas = {} + + if schema_name in schemas: + del schemas[schema_name] + else: + raise ValueError( + f'Schema :{schema_name}: not found in the saved schemas') + + with open('schemas.barfi', 'wb') as handle_write: + pickle.dump(schemas, handle_write, protocol=pickle.HIGHEST_PROTOCOL) \ No newline at end of file diff --git a/docs/source/conf.py b/docs/source/conf.py index 7151374..0f0489e 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -22,7 +22,7 @@ author = 'Adithya Krishnan' # The short X.Y version. -version = '0.6.0' +version = '0.7.0' # The full version, including alpha/beta/rc tags. release = 'alpha' diff --git a/setup.py b/setup.py index 51d960f..5eee903 100644 --- a/setup.py +++ b/setup.py @@ -9,7 +9,7 @@ setuptools.setup( name="barfi", - version="0.6.1", + version="0.7.0", author="Adithya Krishnan", author_email="krishsandeep@gmail.com", description="Framework for a graphical programming environment.", diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_app.py b/tests/test_app.py index 450fc9e..69d1cc2 100644 --- a/tests/test_app.py +++ b/tests/test_app.py @@ -5,13 +5,29 @@ import streamlit as st from test_blocks import base_blocks, base_blocks_category + + barfi_schema_name = st.selectbox( 'Select a saved schema to load:', barfi_schemas()) compute_engine = st.checkbox('Activate barfi compute engine', value=False) -barfi_result = st_barfi(base_blocks=base_blocks_category, compute_engine=compute_engine, -load_schema=barfi_schema_name) +barfi_result = st_barfi(base_blocks=base_blocks_category, compute_engine=compute_engine, load_schema=barfi_schema_name) if barfi_result: st.write(barfi_result) + st.write(barfi_result['Feed-1']['block'].get_interface('Output 1')) + st.write(barfi_result['Feed-2']['block'].get_interface('Output 1')) + st.write(barfi_result['Splitter-1']['block'].get_interface('Input 1')) + st.write(barfi_result['Splitter-1']['block'].get_interface('Output 1')) + st.write(barfi_result['Splitter-1']['block'].get_interface('Output 2')) + st.write(barfi_result['Mixer-1']['block'].get_interface('Input 1')) + st.write(barfi_result['Mixer-1']['block'].get_interface('Input 2')) + st.write(barfi_result['Mixer-1']['block'].get_interface('Output 1')) + st.write(barfi_result['Result-1']['block'].get_interface('Input 1')) + + st.write(barfi_result['Feed-1']['block'].get_state('info')) + st.write(barfi_result['Feed-2']['block'].get_state('info')) + st.write(barfi_result['Splitter-1']['block'].get_state('info')) + st.write(barfi_result['Mixer-1']['block'].get_state('info')) + st.write(barfi_result['Result-1']['block'].get_state('info')) \ No newline at end of file diff --git a/tests/test_blocks.py b/tests/test_blocks.py index 4878bde..43b3c97 100644 --- a/tests/test_blocks.py +++ b/tests/test_blocks.py @@ -24,7 +24,7 @@ def splitter_func(self): mixer.add_input() mixer.add_input() mixer.add_output() -def mixer_func(self): +def mixer_func(self): in_1 = self.get_interface(name='Input 1') in_2 = self.get_interface(name='Input 2') value = (in_1 + in_2)