Skip to content

Binding variables

It will often be useful to display information stored in application data.
To achieve this goal, Taipy allows visual elements to relate directly to application variables, display their values, and change those variable values.

Consider the following application:

1
2
3
4
5
6
7
8
9
from taipy import Gui

x = 1234

Gui(page="""
# Hello Taipy

The variable `x` contains the value <|{x}|>.
""").run()

When this program runs (and a web browser is directed to the running server), the root page displays the value of the variable x, as it was defined in your code.

Expressions

Values that you can use in controls and blocks can be more than raw variable values. You can create complete expressions, just like you would use in the f-string feature (available since Python 3).

In the example code above, you could replace <|{x}|> by <|{x*2}|>, and the double of x will be displayed on your page.

Arbitrary expressions

You can create complex expressions such as |{x} and {y}| to concatenate two variable values, or whatever your imagination and application requirements are.

Formatting

F-string formatting is also available in property value expressions:

  • If you have declared pi = 3.141592653597, then <|Pi is {pi:.4f}|> will display the text:
    Pi is 3.1416.
  • If you have v = 64177, then <|dec:{v}, oct:{v:08o}, hex:{v:X}|> will result in displaying the text:
    dec:64177, oct:00175261, hex:FAB1.

Note that since HTML text eliminates non-significant white space, the right-justification format ({string:>n}) does not impact the resulting display.

Scope for variable binding

In the tiny example above, the entire application holds a single page. The bound variable (x) is defined in the same, unique module.

In larger applications, we may want to create Python modules that hold Taipy pages, binding visual elements to local variables. That allows for a clearer organization of the code, where application variables used in only a few pages can sit next to the page definition.

When Taipy finds a variable in a page (that is, in a Python expression set to a property using the curly braces syntax: {expression}), it first tries to locate it in the module where this page is defined. If the variable can not be found in the page module, then the variable is sought in the __main__ module (typically, where the Gui instance is created).
We call page scopes the context where the variables are located: first, the module where the page is defined, then the main module.

Defining a page scope

A page scope, where variables used in a page definition are searched, is the module where the page instance (an instance of the Markdown or the Html classes), not the text of the page.

This mechanism allows pages to bind to local variables declared on their own module. These variables may not be exposed to other modules of the application.
Global variables (the ones declared in the __main__ module), on the other hand, can be used in all page content definitions. The module where the page is defined does not need to import the global variables if they are not used in the Python code of the module.

Example

Say you want to create a page that represents some evaluation of an expression using two variables. The first variable would be global for the application. But the second variable does not need to be exposed to the rest of the application, so we store it as a local variable in the module where the page is defined.
We create a pages package, where we can create the file page.py, which is the module file where the page would be declared:

pages/page.py
1
2
3
4
5
6
7
from taipy.gui import Markdown

page = Markdown("""# Expression evaluation
Expression value: <|{base_value + local_value}|>
""")

local_value = 0

Note that the page definition uses the variable base_value (in line 4), which is unknown to this module.
The page also references the variable local_value (line 7), defined locally in this module.

Now let's create the main module of the application, adding a home page, and reusing the page that we just created above. That would be done in the file main.py, defining the __main__ module of our application:

main.py
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
from taipy.gui import Gui, Markdown
from pages.page import page

navigation = [("/home", "Home"), ("/page", "Expression")]
root_page="""
<|navbar|lov={navigation}|>
"""

home_page="""# Home page
...
"""

base_value = 0

pages={
    "/": Markdown(root_page),
    "home": Markdown(home_page),
    "page": page
}

Gui(pages=pages).run()

page is imported from pages.page (that is the file pages/page.py) in line 2, then added to the Gui instance in line 18.

When you run the application, you will see that the 'Expression' page can evaluate the expression stored in its text element. The local variable local_value was never exported from the page module, and neither was the global base_value imported in page.
Nevertheless, the evaluation of the expression is performed properly, using those two variables.

Local callback functions

This principle applies to callback functions as well: when a function name is found in a page definition, this function is first searched in the page scope (the module where the page was defined). If the function cannot be located, it is sought in the main module.

This makes it possible to use the same callback function name in different modules and rely on page scopes to determine which is invoked when a callback is triggered: Taipy GUI favors the callback functions that are defined in the module where the page itself is defined.

Global callback functions

The way you can define local callback functions does not apply to global callbacks such as on_action(). There can be only one global callback, defined in the main module.

However, a global callback can call any function in any module where a page would have been define, and the state will behave as you would expect, letting you access the variables from the page scope.

Here is an example to demonstrate that. Suppose you need to initialize some variables from a page defined in a module. The proper way to do this would be to define a function called on_init() and initialize the state variable. However, because on_init() is a global callback and would not be invoked in the module (contrary to when it is declared in the __main__ module), you need to declare an initialization function in the page module (no matter what its name is) and invoke this function from the global on_init() callback.

The code for the module would look like this:

page.py
...
my_variable = ""
...
def on_init(state):
    state.my_variable = "The initial value"
...
sub_page=Markdown("""  ... some content referencing {my_variable} ... """)

Note that we have called the initialization function on_init(), but that's really just a way to make the code easier to understand: what we want to do here is exactly what should be done in the global on_init callback.

Here is how the main module would invoke this initialization function:

main.py
# Create an alias for on_init() in the page module
from page import on_init as page_on_init

def on_init(state):
    page_on_init(state)

Now when the application is initialized from the main module, the global on_init() function invokes the page module's on_init() function with the received state. The access to the state in the page module's on_init() function will properly impact the variable in the correct page scope.

Example

Here is an example of how this local callback binding is done. This example has a main module that holds a single page (at the root of the application URL) with a navbar control that can navigate to the page sub_page, imported from another module.
The root page also has a button that, when pressed, invokes the callback function button_pressed():

main.py
from taipy.gui import Gui, Markdown
from page import sub_page

navigation = [("/sub_page", "Page")]

def button_pressed(state):
    ...

root_page="""# Application
<|navbar|lov={navigation}|>

<|Press for action|button|on_action=button_pressed|>
"""

Gui(pages={
        "/": Markdown(root_page),
        "sub_page": sub_page
    }).run()
"""

Here is the code for the page module, where sub_page is defined:

page.py
from taipy.gui import Markdown

def button_pressed(state):
    ....

sub_page=Markdown("""# Sub-Page
    <|Press for action|button|on_action=button_pressed|>""")

All this module does is define the (local) function button_pressed(), and the sub_page page that the main module imports.

Now both the root page and the sub-page have a button, defined so they would invoke a callback function called button_pressed() when activated.
The Taipy GUI implementation is such that if you press the button from the root page (defined in the main module), then it is the global function button_pressed() that gets called. If you press the button from the sub page (defined in the page module). then it is the function button_pressed() defined in the page module that gets called.

Page scope in callbacks

All callback functions are invoked with a state object as their first parameter.
From that state, one can access the variables of the application. However, the state interprets the access to variables depending on the page scope: if a callback is defined in a module, then local variables will be available directly, scoped to the appropriate module.

In the example above, we have defined two different callback functions called button_pressed(). Suppose we had defined two variables called x in both the main module and the sub-page module, then used x in both the root page and the sub-page, then when invoked, the state received in the callback functions would behave slightly differently, enforcing the use of the page scope:

  • When the button_pressed() defined in the main module is invoked, then state.x would return the value of the x variable, in the main module.
  • When the button_pressed() defined in the sub-page module is invoked, then state.x would return the value of the x variable, in the sub-page module.

This makes it very obvious to focus on the variable at hand in a given module where a page is defined.

Crossing page scopes

The state received in callbacks has a mechanism that lets you access variables from other modules.
The way this works is to use the state object as a collection, indexed by the name of the module where the variable is defined (and used in a page definition).

In the situation described above, no matter which button_pressed() callback function is invoked, you can always explicitly reference the x variable defined in the sub-page module using the syntax: state["page"].x. The indexed syntax avoids confusion.
Similarly, state["__main__"].x always refers to the global x variable, defined in the main module.

List of values

Some controls (such as selector or tree) represent one or several values stored in a list of values (or LoV, for short). This notion is a way of describing a set of values that controls can show and select.

Each item in a list of values can hold:

  • An identifier: an optional string that uniquely identifies an item in the entire list;
  • A label: a string that is used when displaying the specific item;
  • An image: an optional Icon that can be used to display the item as a small image. Note that icons can also hold a descriptive string.

A LoV can have different types, depending on the use case:

  • List of strings: if your items are just a series of strings, you can create a LoV as a List[str] or a single string, where item labels are separated by a semicolon (';');

  • List with identifier: in situations where items labels may appear several times in the same LoV, you will need a unique identifier to specify which item is represented.
    In this situation, the LoV will be a list of tuples where each tuple will contain:

  • A unique identifier as the first item;

  • A label as the second item.

Therefore, the Python type of such a LoV is List[Tuple[str, str]].

  • List with images: if you need to represent items with images, you will create a LoV that is a list of tuples where each tuple will contain:

  • A unique identifier as the first item;

  • An Icon as the second item.

The Python type of such a lov is: List[Tuple[str, Icon]].

The "selected" value in controls that use LoVs are handled in their value property. This will be the original value of the selection in the LoV unless your control has set the property value_by_id to True, then the selected value will be the unique identifier of the value in the LoV.

Custom LoVs

You can create a LoV from any series of objects.

In this situation, you will provide the control with your list of objects in the property lov, and an adapter function that transforms each item of the list into a tuple that the control can use as a 'regular' LoV.

An adapter is a Python function that receives each object of a LoV and that must return a tuple similar to the ones used in 'regular' LoVs.

Example

Suppose you want to display a control representing versions of the Python programming language. You want to display this list in selector and let users select a given version.

The list of values is an ordered list of descriptors:

python_versions=[
  { "name": "0.9.0", "date": 1991 },
  { "name": "1.0",   "date": 1994 },
  { "name": "1.1",   "date": 1994},
  { "name": "1.6",   "date": 2000 },
  { "name": "2.0",   "date": 2000 },
  { "name": "2.2",   "date": 2001 },
  { "name": "2.3",   "date": 2003 },
  { "name": "2.7",   "date": 2010 },
  { "name": "3.0",   "date": 2008 },
  { "name": "3.4",   "date": 2014 },
  { "name": "3.8",   "date": 2019 },
  { "name": "3.9",   "date": 2020 },
  { "name": "3.10",  "date": 2021 },
  { "name": "3.11",  "date": 2022 }
]

You need to represent each item as: "Version <version name> (<version date>)". The identifier for each version will be its name, which is unique.

The adapter you need to write would look like this:

def python_version_adapter(version):
  return (version["name"], f"Version {version["name"]} ({version["date"]})")

LoV for trees

The tree control needs an additional item in each value of the LoV: each element of the LoV represents a node in the tree, and the additional element in each node's tuple must hold the child nodes as another LoV, or None if it does not have any.

Here is an example of how a LoV for trees can be created.

We want to provide a control that allows the selection of a musical instrument from a tree control where instruments are classified:

selected_intrument = None
intruments = [
  ("c1", "Idiophones", [
    ("t1-1", "Concussion", ["Claves", "Spoons"]),
    ("t1-2", "Percussion", ["Triangle", "Marimba", "Xylophone"]),
    ("t1-3", "Plucked", ["Pizzicato "])
  ]),
  ("c2",  "Membranophones", [
    ("t2-1", "Cylindrical drums", ["Bass drum", "Dohol"]),
    ("t2-2", "Conical drum", ["Timbal"]),
    ("t2-3", "Barrel drum", ["Dholak", "Glong thad"])
  ]),
  ("c3",  "Chordophones", [
    ("t3-1", "Plucked", ["Guitar", "Harp", "Mandolin"]),
    ("t3-2", "Bowed", ["Violin", "Cello", "Jinghu"])
  ]),
  ("c4",  "Aerophones", [
    ("t4-1", "Flute", ["Piccolo", "Bansuri", "Transverse flute"]),
    ("t4-2", "Reed", ["Bassoon", "Oboe", "Clarinet"]),
    ("t4-3", "Brass", ["Trombone", "Trumpet", "Cornett"])
  ])
]
The tree items are stored in the variable instruments, and selected_instrument will be bound to the tree selection.

The Markdown fragment that would be used in a page would look like this:

<|{selected_intrument}|tree|lov={intruments}|>

Tabular values

The chart and table controls represent tabular data. This data can be provided as a Pandas DataFrame, a list, a NumPy array, or a dictionary.

When a variable containing such tabular data is bound to a control, it is internally transformed into a Pandas DataFrame so one can specify which series of data should be used by a control bound to that data: for a table, you can indicate which data a column displays. In a chart, you can have different traces for different series in the same graph.

Note that the Python list comprehension syntax (i.e. expressions such as [x for x in range(0, 10)]) is appropriately handled, as long as the returned type is one of the supported types. Such expressions can even be inlined directly in the definition of the properties of your control.

This is how Taipy internally stores the tabular data:

  • If the data is a dictionary, Taipy creates a DataFrame directly from it.
  • If the data is a list of scalar values, Taipy creates a DataFrame with a single column called '0'.
  • If the data is a list of lists:
  • If all the lists have the same length, then Taipy creates a DataFrame with one column for each list, in the order of the list data. The column names are the list index.
  • If the lengths of the lists differ, Taipy creates an internal list of DataFrames, that each has one single column. The name of the DataFrame column is <index>/0, where <index> is the index of the list in the original data list.

List of lists in charts

To display several traces within the same chart control, you must use a list of tabular values.

Column names

When dealing with a list of tabular data, you need to access the name of the data columns to provide them to the controls that use them.

To select a column for a control, you must provide the name of this column, prefixed with the index of the data and a slash character.
For example, suppose your data set is defined with the following Python code:

dataframe_1 = pd.DataFrame({'x': [i for i in range(10)],
                            'y': [i for i in range(10)]} )
dataframe_2 = pd.DataFrame({'x': [i for i in range(10)],
                            'y': [i*i for i in range(10)]})
data = [ dataframe_1, dataframe_2 ]
In controls that are bound to data, the name of the 'y' column of the second DataFrame must be the string "1/y" .

Lambda expressions

Some control properties can be assigned lambda expressions to simplify the code.

You can, for example, bind a lambda function to the on_action property of a control. Here is how you can do this with Markdown:

<|Action|button|on_action={lambda state: ...}|>
The lambda expression receives the same parameters as the regular on_action callback function.

Because Python prevents any assignment from being performed in the body of a lambda expression, you cannot update the state object directly. However, the State class exposes the method assign() that allows working around that limitation. Here is how you can change the value of the variable toggle directly from the Markdown definition of your page, within the body of the definition of a button:

<|Toggle|button|on_action={lambda state: s.assign("toggle", not state.toggle)}|>
Pressing the button will toggle the value of the toggle variable.