Funix - The laziest way to build GUI apps in Python
Make it (the app) before they fake it (in Figma or on a napkin)
Abstract¶
The rise of machine learning (ML) and artificial intelligence (AI), especially the generative AI (GenAI), has increased the need for wrapping models or algorithms into GUI apps. For example, a large language model (LLM) can be accessed through a string-to-string GUI app with a textbox as the primary input.
Most of existing solutions require developers to manually create widgets and link them to arguments/returns of a function individually. This low-level process is laborious and usually intrusive. Funix automatically selects widgets based on the types of the arguments and returns of a function according to the type-to-widget mapping defined in a theme, e.g., bool
to a checkbox. Consequently, an existing Python function can be turned into a GUI app without any code changes. As a transcompiler, Funix allows type-to-widget mappings to be defined between any Python type and any React component and its props
, liberating Python developers to the frontend world without needing to know JavaScript/TypeScript. Funix further leverages features in Python or its ecosystem for building apps in a more Pythonic, intuitive, and effortless manner. With Funix, a developer can make it (a functional app) before they (competitors) fake it (in Figma or on a napkin).
1Introduction¶
Presenting a model or algorithm as a GUI application is a common need in the scientific and engineering community. For example, a large language model (LLM) is not accessible to the general public until it is wrapped with a chat interface, consisting of a text input and a text output. Since most scientists and engineers are not familiar with frontend development, which is JavaScript/TypeScript-centric, there have been many solutions based on Python, one of the most popular programming languages in scientific computing, especially AI. Examples include ipywidgets, Streamlit, Gradio, Reflex, Dash, and PyWebIO. Most of these solutions follow the conventional GUI programming philosophy, requiring developers to manually select widgets from a widget library and associate them with the arguments and returns of an underlying function, commonly referred to as the “callback function.”
This approach has several drawbacks.
First, it is manual and repetitive. A developer needs to manually create widgets, align them with the signature of the callback function, and maintain the alignment manually should any of the two changes.
Second, the choice of widgets is limited to those provided by a specific GUI library, as expanding the widget library requires significant knowledge and effort that the target users are unlikely to possess.
Third, these solutions do not leverage the features of the Python language itself to automate or streamline the process. For example, most existing solutions require developers to manually specify the labels of widgets, even though such information is often already available in the Parameters
or Args
sections of a function’s Docstrings, which are common in Python development.
As a result, scientific developers, such as geophysicists, neurobiologists, or machine learning engineers, whose jobs are not building apps, are not able to quickly fire up apps to present their models, algorithms, or discoveries to the world.
To address these drawbacks, Funix was created to automatically launch apps from existing Python functions. We observed that the choice of a widget is correlated with the type of the function’s input/output that it is associated with. For example, a checkbox is only suitable for Boolean types. Funix automatically selects widgets based on the types of the function’s arguments and returns. In the default theme of Funix, Python native types such as str
, bool
, and Literal
are mapped to an input box, a checkbox, and a set of radio buttons in the MUI library, respectively. Common scientific types like pandas.DataFrame
and matplotlib.figure.Figure
are mapped to tables (MUI’s DataGrid
) and charts (in mpld3
). A variable’s type can be specified via type-hinting, which is a common practice in Python development, or inferred, which Funix plans to support in the future.
Unlike many of its peers, Funix does not have its own widget library.
Any React component can be bound to a Python type.
A type-to-widget mapping can be redefined or created [on-the-fly](#Supporting a new type on the fly using the new funix type decorator) or via a reusable [theme](#Defining and using themes).
Additionally, the properties (i.e., props
) of the frontend widget can be configured in Python via JSON.
In this sense, Python becomes a surface language for web development.
The frontend development world, dominated by JavaScript/TypeScript, is now accessible to Python developers who previously couldn’t tap into it.
The centralized management of appearances using themes is an advantage of Funix over many of its peers. A theme allows for the automatic launching of apps and ensures consistent GUI appearances across different apps. To change the appearance of an app, simply select a different theme. For further customization, a theme can be extended using the popular JSON format. Often, a Funix developer can leverage themes developed by others to support new types or widgets, similar to how most scientists use LaTeX classes or macros created by others to properly typeset their papers.
More than a GUI generator, Funix is a transcompiler -- a program that translates source code from one language to another -- which generates both the backend and frontend of an application. Funix wraps the Python function into a Flask app for the backend and generates React code for the frontend. The two ends communicate via WebSocket.
In addition to types, Funix leverages other features of the Python language and ecosystem to further automate app building. Docstrings, commonly used in Python, are utilized by Funix to control the UI appearance. For example, the annotation of an argument in the Args
(Google-style) or Parameters
(Numpy-style) section of a docstring is used as the label or tooltip to explain the argument to the app user.
Funix also assigns new meanings to certain keywords and types in Python within the context of app building. For instance, global
is used for states and sessions, yield
for streaming, and each class becomes a multi-page app where pages share data via the self
variable. These concepts are detailed in Section Building apps Pythonically.
In summary, Funix has the following cool features for effortless app building in Python:
- Automatic, type-based GUI generation controlled by themes
- Exposing any React component to Python developers
- Leveraging Python’s native features to make app building more intuitive, minimizing the need to learn new concepts specific to Funix.
2Motivation: the demand to rapidly launch low-interactivity and plain-looking apps at scale¶
When it comes to GUI app development, there is a trade-off between simplicity and versatility. JavaScript/TypeScript-based web frontend frameworks like React, Angular, and Vue.js offer great versatility. However, their versatility is often beyond the reach of most scientists and engineers, except for frontend or full-stack developers, and is usually overkill for most scientific and engineering applications.
As machine learning researchers, we have observed that a significant number of scientific applications share two common features:
- The underlying logic is a straightforward input-output process -- thus complex interactivity, such as dynamically updating an input widget based on user input in another input widget, is not needed.
- The app itself is not the end goal but a means to it -- thus it is not worthy to spend time on building the app.
Existing Python-based solutions such as Streamlit or Gradio do a great job addressing the first feature but are still too complicated for the second one, requiring developers to read their documentation and add code before an app can be launched. In particular, a developer needs to manually select and configure widgets and/or link them to the arguments and returns of a function. This low-level process is laborious. Since versatility is already sacrificed for simplicity in Python-based app building, why not trade it further for even more simplicity?
Funix pushes simplicity to the extreme. In the hello, world example above, an app is launched without requiring any learning or code modification. To achieve this simplicity, Funix leverages common Python development practices, such as type hints (or type inference) and docstrings, to save developers from extra work or learning. Funix does not aim to become a Domain Specific Language (DSL), because it treats the Python programming language itself (including the typing hint syntax and docstring styles) as a surface language for GUI app building.
Because Funix is designed for quickly firing up apps that model straightforward input-output processes, a Funix-generated app consists of two panels: the arguments of the underlying function on the left, input panel and the returns and printouts on the right, output panel (Program 1 and Figure 1). A more complex process can be decomposed into simple input-output processes and embodied into multi-page apps. The underlying or callback function will be called after the user plugs in the arguments and click the “Run” button. The result will be displayed in the output panel.
We would also like to argue that the rise of Generative AI (GenAI) is simplifying GUIs, as natural languages are becoming a prominent interface between humans and computers. For example, a text-to-image generation app (Figure 3) only needs a string input and an image output. In this sense, Funix and its Python-based peers will be able to meet many needs, both in scientific computing and more general applications, in the future.
3Funix’s default mapping from Python types to React components¶
Like CSS, Funix controls the GUI appearance based on the types. Under the hood, Funix transcompiles the signature of a function into React code. Currently, Funix depends on the type hint for every argument or return of a function. In the future, it will support type inference or tracing.
The default mapping from basic Python types to React components is given in Table 1. In particular, we leverage the semantics of Literal
and List[Literal]
for single-choice and multiple-choice selections. Two apps exhibiting the diversity of widgets are shown in Figure 2 and Figure 9.
Table 1:Default mapping from basic Python types to components.
Python type | As an input (argument) or output (return) | Widget |
---|---|---|
str | Input | MUI TextField |
bool | Input | MUI Checkbox or Switch |
int | Input | MUI TextField |
float | Input | MUI TextField or Slider |
Literal | Input | MUI RadioGroup if number of elements is below 8; Select otherwise |
range | Input | MUI Slider |
List[Literal] | Input | An array of MUI Checkboxes if the number of elements is below 8; AutoComplete otherwise |
str | Output | Plain text |
bool | Output | Plain text |
int | Output | Plain text |
float | Output | Plain text |
Because Funix is a transcompiler, it leverages multimedia types already defined in popular Python libraries such as ipywidgets
(Jupyter’s input widgets), IPython
(Jupyter’s display system), pandas
, and matplotlib
. ipywidgets
and IPython
types are mapped to MUI components rather than their respective components for being React compatible. Figure 4 illustrates a data-plot-from-tabular-data app that maps a pandas.DataFrame
to a table and a matplotlib.figure.Figure
to a chart.
Table 2:Default mapping from common multimedia/MIME types to components.
Python type | As an input (argument) or output (return) | Widget |
---|---|---|
ipywidgets.Password | Input | MUI TextField with type="password" |
ipywidgets.Image | Input | React Dropzone combine with MUI Components |
ipywidgets.Video | Input | React Dropzone combine with MUI Components |
ipywidgets.Audio | Input | React Dropzone combine with MUI Components |
ipywidgets.FileUpload | Input | React Dropzone combine with MUI Components |
IPython.display.HTML | Output | Raw HTML |
IPython.display.Markdown | Output | React Markdown |
IPython.display.JavaScript | Output | Raw JavaScript |
IPython.display.Image | Output | MUI CardMedia with component=img |
IPython.display.Video | Output | MUI CardMedia with component=video |
IPython.display.Audio | Output | MUI CardMedia with component=audio |
matplotlib.figure.Figure | Output | mpld3 |
pandas.DataFrame & pandera.typing.DataFrame | Input & Output | MUI DataGrid |
4Customizing the type-to-widget mapping¶
The default mapping from Python types to React components is detailed in the section above. To expand or modify an existing mapping, Funix provides two approaches: the on-the-fly, decorator-based approach and the reusable, theme-based approach.
As a transcompiler that generates React code, Funix does not have its own widget library.
Instead, developers can choose any React component on the market (as for now, only some MUI components are supported.) to use as the widget for a Python type.
Additionally, Funix allows configuring the properties (props
) of the frontend widget in Python via JSON.
In this way, Funix bridges the Python world with the frontend world, making Python or JSON the surface language for React-based frontend development.
4.1Supporting a new type on the fly using the new_funix_type
decorator¶
Program 5 defines a new type blackout
, which a special case (as indicated by the inheritance) of str
and binds it with a widget. Following the convention in the frontend world, Funix identifies a widget by its module specifier in npm
, the de facto package manager in the frontend world. In Program 5, the widget is identified as @mui/material/TextField
. Properties of the widget supported by its library for configuration can be modified in the new_funix_type
decorator as well. As mentioned earlier, this allows a Pythonista to tap into a React component without frontend development knowledge.
The on-the-fly approach is only applicable when introducing a new type, e.g., a custom class. To modify the widget choice for an existing type, a theme must be used.
4.2Defining and using themes¶
A type-to-widget mapping can be reused and centralized managed via a theme, which is a simple JSON file. An example is given in Program 6 below where the Python’s native types str
, int
, and float
are bound to three widgets. In this exmaple, besides using npm
module specifier, Funix shorthand strings inputbox
and slider
are also used.
There are two ways to apply a theme: script-wide and function-wide. The script-wide approach (Program 7) applies a default theme to all functions in a script. The function-wide approach (Program 8) applies a theme to a specific function. In either case, the theme can be referred to by a web URL, a local file path, or a name/alias. If no theme is specified, Funix uses its default theme.
To refer to a theme by its name or alias, it must be imported. The alias can be set when importing. A theme can be imported from a web URL, a local file path, or a JSON dictionary defining a theme (Program 9).
5Building apps Pythonically¶
Funix leverages the language features of Python and common practices in Python development to make app building in Python more intuitive and efficient.
5.1Default values as placeholders¶
Python supports default values for keyword arguments. Funix directly uses them as the placeholder values for corresponding widgets. For example, default values in Program 2 are prefilled in Figure 2. A more complex example is the pandas.DataFrame
initated with numpy
columns in Program 4 are prefilled into the table in Figure 4. In contrast, Funix’ peer solutions require developers to provide the placeholder values the second time in the widget initiation.
5.2Making use of docstrings¶
Docstrings are widely used in the Python community. Information in docstrings is often needed in the GUI of an app. For example, the annotation of each argument can be displayed as a label or tooltip to explain the meaning of the argument to the app user. Therefore, Funix automatically incorporates selected information from docstrings into apps, eliminating the need for developers to manually add it, as required by other solutions.
Different sections of docstrings will be transformed into various types of information on the frontend.
The docstring above the first headed section (e.g., Parameters
in this example) will be rendered as Markdown.
Argument annotations in the Args
section will become labels in the UI.
The Examples
section will provide prefilled example values for users to try out.
Funix currently supports only Google-style and Numpy-style docstrings.
Supports for sections beyond Args
/Parameters
and Examples
and styles will be added in the future.
5.3Output layout in print
and return
¶
In Funix, by default, the return value of a function becomes the content of the output panel.
A user can control the layout of the output panel by returning strings, including f-strings, in Markdown and HTML syntaxes.
Markdown and HTML strings must be explicitly specified as IPython.display.Markdown
and IPython.display.HTML
, respectively.
Otherwise, the raw strings will be displayed.
Since Python supports multiple return values, you can mix Markdown and HTML strings in the return statement.
Quite often, we need to print out some information before a function reaches its return statement. The print
function, a built-in feature of Python, is frequently used for this purpose.
Funix extends this convenience by redirecting the output of print
to the output panel of an app.
Printout strings in Markdown or HTML syntax will be automatically rendered after syntax detection. To avoid conflicting with the default behavior of printing to stdout
, printing to the web needs to be explicitly enabled using a Boolean decorator parameter print_to_web
(See Program 11).
5.4Streaming based on yield
¶
In the GenAI era, streaming is a common method for returning lengthy text output from an AI model.
Instead of inventing a new mechanism to support streaming, Funix repurposes the yield
keyword in Python to stream a function’s output.
The rationale is that return
and yield
are highly similar, and return
is already used to display the final output in a Funix-powered app.
5.5States, sessions, and multi-page apps¶
5.5.1A simple approach by using global
¶
In Funix, maintaining states is as simple as updating a global
variable.
By leveraging the semantics of global
variables which are a native feature of the Python language, Funix saves developers the burden of learning something new in Funix.
A security risk is that there is only one backend server for the Funix app and consequently a global
variable is accessible by all browser sessions of the app.
To eliminate this risk, Funix provides a simple command-line flag -t
at the launch of the Funix app to sessionize all global
variables.
If the developer on purpose wants to share the data among different connections, the -t
flag can be omitted.
5.5.2Multi-page apps from classes¶
A special but useful case that requires maintaining states is multi-page apps. A multi-page app consists of multiple pages or tabs that share the same variable space. Values of variables set on one page can be accessed on another page.
Without reinventing the wheel, Funix supports this need by turning a Python class into a multi-page app.
Each member function of the class becomes a page of the multi-page app, and pages can exchange data via the self
variable.
Specifically, the constructor (__init__
) becomes the landing page of the multi-page app.
Since each instance of the class is independent, the multi-page app is automatically sessionized for different connections from browsers without needing to set the -t
flag.
Program 14 is a Python class of three member methods, which are turned into three pages of the GUI app by Funix (Figure 8). The GIF shows that values of self.a
set in either the contructor or updated in the set
method can be accessed in the get
method.
In this approach, a Funix developer does not need to learn anything new and can easily build a mulit-page app from the OOP principles they are already familiar with.
6The Funix decorators¶
Although Funix relies on the Python language (including type hints and docstrings) to define GUI apps,
there are still some aspects of an app’s appearance and behavior that remain uncovered.
This is where the @funix
decorator comes into play. One example, as mentioned above (Section 5.3), is redirecting the print
output from stdout
to the output panel of an app. Here, we provide a few more examples. For full details on Funix decorators, please refer to the Funix reference manual.
6.1Overriding the type-based widget choice¶
Funix uses types to determine the widgets. However, there may be needs to manually pick a widget. The @funix
dectorator has a widgets
parameter for this purpose.
Program 15 is an example to temporarily override the widget choice for two variables of the types Literal
and List[Literal]
respectively. The corresponding app (Figure 9) is a sentence builder. The Funix-based code is much shorter and more human-readable than its Gradio-based counterpart, thanks to leveraging the Python-native features like automatic rendering the return
strings or default values.
6.2Automatic re-run triggered by input changes¶
As mentioned earlier, Funix is suitable for straightforward input-output processes. Such a process is triggered once when the “Run” button is clicked. This may work for many cases but in many other cases, we may want the output to be updated following the changes in the input end automatically.
To do so, simply toggle on the autorun
parameter in the @funix
decorator. This will activate the “continuously run” checkbox on the input panel.
6.3Conditional visibility¶
Although interactivity is not a strong suit of Funix for reasons aforementioned, Funix still supports some common interactivity features. One of them is “conditional visibility” which reveal some widgets only when certain conditions are met (Program 17 and Figure 11).
6.4Rate limiting¶
When an app is exposed, a common concern is how to avoid abuses. Rate limiting is a common measure to this. Funix’s @funix
decorator supports rate limiting based on both browser sessions and time.
6.5Reactive apps¶
Funix can dynamically prefill widgets based on information from other widgets. We call this “reactive.” An example is given in Program 18. The tax
argument of the function is populated automatically based on the values of salary
and income_tax_rate
as the user enters.
6.6Showing source code¶
Lastly, togging on show_source
parameter in @funix
can enable the source code of your app to be displayed.
7Jupyter support¶
Jupyter is a popular tool for Python development. Funix supports turning a Python function/class defined in a Jupyter cell into an app inside Jupyter.
To do so, simply add the @funix
decorator to the function/class definition and run the cell (Figure 13).
8Showcases¶
Lastly, please allow us to use some examples to demonstrate the convenient and power of Funix in quickly prototyping apps. If there is any frontend knowledge needed, it is only HTML.
8.1Wordle¶
The source code can be found here. In Funix, only simple HTML code that changes the background colors of tiles of letters according to the rules of the game Wordle is needed. A GIF showing the game in action is in Figure 14.
8.2ChatGPT multi-turn¶
Funix does not have a chat widget, because it is so easy (less than 10 lines in Program 19) to build one using simple alignment controls in HTML. The only thing Funix-specific in the code is using the @funix
decorator to change the arrangement of the input and output panels from the default left-right to top-down for a more natural chat experience.
8.3Multimodal inputs¶
Funix extends the support to ipywidgets.{Image, Audio, File, Video}
to allow drag-and-drop of multimedia files or push-to-capture audio or video from the computer’s microphone or webcam.
8.4Vector stripping in bioinformatics¶
A vector is a nucleotide sequence that is appended to a nucleotide sequence of interest for easy handling or quality control. It is added before the sequencing process and should be removed after the sequence is read. Vector stripping is the process of removing vectors. A vector stripping app only involves simple data structures, such as strings, lists of strings, and numeric parameters. This is a sweet spot of Funix.
Because the bioinformatics part of vector stripping is lengthy, we only show the interface function in Program 21 and the full source code can be found here. pandas.DataFrame
’s are used in both the input and output of this app, allowing biologists to batch process vector stripping by copy-and-pasting their data to Excel or Google Sheets, or uploading/downloading CSV files.
9Conclusion¶
In this paper, we introduce the philosophy and features of Funix. Funix is motivated by the observations in scientific computing that many apps are straightforward input-output processes and the apps are meant to be disposable at a large volume. Therefore, Funix’ goal is to enable developers, who are experts in their scientific domains but not in frontend development, to build apps by continue doing what they are doing, without code modification or learning anything new. To get this goal, Funix leverages the language features of the Python language, including docstrings and keywords, to automatically generate the GUIs for apps and control the behaviors of the app. Funix tries to minimize reinventing the wheel by being a transcompiler between the Python word and the React world. Not only does it expose developers to the limitless resources in the frontend world, but it also minimizes the learning curve. Funix is still a very early-stage project. As an open-source project, we welcome feedback and contributions from the community.
Acknowledgments¶
Funix is not the first to exploit variable types for automatical UI generation. Python Fire by Google is a Python library that automatically generates command line interfaces (CLIs) from the signatures of Python functions. Funix extends the idea from CLI to GUIs. interact
in ipywidgets infers types from default values of keyword arguments and picks widgets accordingly. But it only supports five types/widgets (bool
, str
, int
, float
, and Dropdown menus) and is not easy to expand the support. We’d like to thank the developers of these projects for their inspiration.