Block
The foundation of Barfi’s Flow-Based Programming (FBP) philosophy is the Block
. Each Block
represents a fundamental component in the data flow program, with inputs, outputs, user-input options and executable functionality. The Block
is designed to be reusable and composable, allowing users to build complex programs by connecting multiple Blocks
together.
A Block
has two primary interfaces to communicate between each other: Inputs and Outputs. These interfaces serve as connections between Blocks
, enabling the exchange of data. Each Block
can carry a custom executable function, which accesses input data, and user-input options and performs computations or calculations using it, and sets output values to the output interfaces.
Barfi’s FBP approach separates the graphical programming interface from the computation environment, enabling seamless integration into existing workflows and scripts. This is unlike traditional visual FBP libraries that confine both GUI and computation to their own environments. Barfi bridges this gap by providing a dedicated graphical interface through a Streamlit widget (with a Jupyter-Notebook widget in development) and a standalone computation environment via the ComputeEngine
.
Block
Implementation Example
Initializing a Block
Block
is a Python dataclass object, and is initialized as by giving it a unique name that will be identified in all further references through this unqiue name.
from barfi.flow import Block
# 01: Initialize a Block
my_block = Block(name='my-block')
Input and Output Interfaces
A Block
has two primary interfaces to communicate between each other: Inputs and Outputs. These interfaces serve as connections between Blocks
, enabling the exchange of data between them. Each input and output must have a unique name within the block to identify and reference them in the workflow.
Add an input interface to my_block
as:
# Creates an input with auto-generated name: 'Input 1'
my_block.add_input()
# Creates an input with custom name: 'my input'
my_block.add_input(name='my input')
While the block will automatically generate unique names like Input 1
, Input 2
, etc., it’s recommended to provide meaningful custom names for better readability and easier reference in your workflow. This is especially important when accessing these interfaces in compute functions.
And, similarly you can add an output as:
# Creates an output with auto-generated name: 'Output 1'
my_block.add_output()
# Creates an output with custom name: 'my output'
my_block.add_output(name='my output')
User-Input Options
User-input options allow blocks to have interactive interfaces for user configuration. These options can be used to customize the block’s behavior during runtime. Barfi supports various types of input interfaces including checkboxes, text inputs, numeric inputs, dropdowns, sliders, and display fields.
Options can be added, modified, and accessed through the following methods:
# Add options to the block
my_block.add_option(name='my_checkbox', type='checkbox', value=True)
my_block.add_option(name='my_text', type='input', value='default text')
my_block.add_option(name='my_number', type='number', value=42.0)
my_block.add_option(name='my_dropdown', type='select', items=['option1', 'option2'], value='option1')
my_block.add_option(name='my_slider', type='slider', min=0, max=100, value=50)
my_block.add_option(name='my_display', type='display', value='Read-only text')
# Access an option's value in compute function
def my_compute(self):
checkbox_value = self.get_option('my_checkbox')
# ... use the value in computations ...
The supported option types are:
checkbox
: Boolean toggleinput
: Text input fieldinteger
: Whole number inputnumber
: Decimal number inputselect
: Dropdown selectionslider
: Range sliderdisplay
: Read-only text display
Detailed information about the options are provided here.
Each option must have a unique name within the block and can include additional parameters depending on its type. For example, sliders require min
and max
values, while select options need an items
list of choices.
Adding a compute function
A compute function is the brain of a Block
- it defines how the block processes data and produces results. As each block is designed to perform a specific, focused task within the larger workflow, the compute function encapsulates the entire functionality of that task, from receiving inputs, processing them and producing outputs.
Each Block
carries an executable function that has access to all the interfaces such as the inputs and outputs, and the user-input options. The function gets this access through a default argument that is passed to it when executed. A typical compute function would get data from the input interface and user-input options, perform computations and set the output values through the output interface.
Let’s look at how a compute function can be added to a Block
, get the input data, perform some computation and set the output data.
def my_block_func(self):
# Get the input interface value for the inputs interfaces that were set above
input_1 = self.get_interface(name='Input 1')
my_input = self.get_interface(name='my input')
checkbox_value = self.get_option('my_checkbox')
output_1 = ... # ... perform some computation
output_my_output = ... # ... perform some computation
# Set the output interface value for the outputs interfaces that were set above
self.set_interface(name='output_1', value=output_1)
self.set_interface(name='output_my_output', value=output_my_output)
# Add the compute function to the block
my_block.add_compute(my_block_func)
The above code compute function first retrieves input values using get_interface()
, which allows it to access data that was sent to the block from previous connections. After getting these inputs, the function can perform any necessary calculations or transformations on the data. Finally, it uses set_interface()
to assign the processed results to the block’s outputs, making them available for any downstream blocks that are connected.
Summary
In summary, a Block
is a fundamental component in Barfi’s framework, representing a building block for data flow programs. With its inputs, outputs, and executable functionality, it can be combined with other Blocks
to create complex programs that are reusable, composable, and adaptable to various use cases.