Handy graphical user interface library for daily use


Keywords
gui
License
MIT
Install
pip install pancakekit==0.2.2

Documentation

Pancake Kit

Pancake Kit aims to provide a handy user interface to your Python codes. It provides GUI as a lightweight web app powered by Flask.

Installation

pip3 install pancakekit

Quick Tasting

Assume that there is a function that calculates the nth Fibonacci number. In three steps, you can prepare a GUI that wraps this function.

from pancakekit import Pancake

def fibonacci(n=10):
    return (fibonacci(n-1) + fibonacci(n-2)) if n >= 2 else n

cake = Pancake()	# Step 1: Make a Pancake instance.
cake.add(fibonacci)	# Step 2: Add your function to the pancake.
cake.serve()		# Step 3: Serve the cake.

When you open http://127.0.0.1:8000/ in a web browser, you will find an input box for entering n and a button that invokes fibonacci().

Adding an interface to your exisiting python script in an instant

pip -m pancakekit __YOUR_SCRIPT_FILE_NAME__

Adding toppings

In Pancake Kit, a Pancake instance corresponds to a single web page. Each UI component is added to the pancake as a Topping instance.

Value

cake.add({"a": 0, "b": 1})	# Dictionary -> Multiple inputs (DictInput)

# String ends with ":" [+ some value] -> Value input with the string as a label
cake.add("a:")
cake.add("b:123")

cake.add("abc")	# String/Number -> Non-editable text (Text)

Special object

cake.add(image)		# PIL.Image/Path string -> ImageView

cake.add(df)		# pandas.DataFrame/Dict of list -> Table

Function

def f(a, b, c):
	return a * b + c

cake.add(f) # Function -> Multiple inputs & execution button

Equivalently, you can decorate the function with a decorator @cake.topping:

@cake.topping
def f(a, b, c):
	return a * b + c

Value input with @decorator

# @slider(range_min, range_max, [step])[:value] -> Slider
cake.add("abc:0.5")

Use Pancake Kit in the interactive mode

In the interactive mode, you can perform on-site decoration on your pancake.

# On the Terminal or Command Prompt
from pancakekit import *
cake = Pancake()
cake.serve()
# -> You will find a blank page on http://127.0.0.1:8000/.

d = cake.add({"a": 1, "b":2, "c":3})
# -> A dictionary input appears on the page immediately.

d.value_changed = cake.show_message
# -> As you change the values of the input, you will see a pop-up message.

As a shortcut to the instantiation & cake.serve(), you can use MagicPancake. The additional feature of the MagicPancake is that you can access the value of a topping by its name.

from pancakekit import *
cake = MagicPancake()

cake.add("myinput", name="a")
cake.a = 10
cake["a"].value_changed = lambda: cake.show_message(cake.a)

More conveniently, execute

python3 -m pancakekit

where the cake object and all the toppings in pancakekit have already been loaded as local variables.

How to

Adding a PancakeKit topping

You can add your favorite toppings to your pancake, as in the following example:

from pancakekit import Pancake, Button, Label

cake = Pancake()
button = cake.add(Button(0))
label = cake.add(Label(1))

def button_click(x):
    button.value = label.value
    label.value += x

button.clicked = button_click

cake.serve()

Here, when a clicked event is sent to button_click, the value of the button is passed as an argument. The value of the topping can be changed by assigning a value to the value property.

Responding to user actions

When a user changes the value of the toppings from the browser, the callback function specified by topping.value_changed is invoked.

from pancakekit import *

cake = Pancake()

dict_input = cake.add({"a": 1, "b": 2, "c": 3})
dict_input.value_changed = lambda: cake.show_message(dict_input.value)

cake.serve()

Note that cake.show_message() will pop up a message in the browser.

Accessing a topping by its name

You can access the toppings in the cake by their names.

from pancakekit import *

cake = Pancake()

cake.add(Slider("a", range_min=0, range_max=20), name="slider0")
cake.add(Slider("b", range_min=0, range_max=20), name="slider1")

cake["slider0"].value_changed = lambda x: setattr(cake["slider1"], "value", x+5)
cake["slider1"].value_changed = lambda x: setattr(cake["slider0"], "value", x*2)

cake.serve()

MagicCard

By holding down the shift key and pressing the return key, MagicCard appears with a text box. You can execute the code in the textbox by the command/control key + return. The pancake can be referred to as cake. Try the following to see if it actually works.

cake.add(Button("Hi"))
cake(cake.toppings) # In the magic card, the direct call of cake() pops up a message.

Toppings

Toppings for user interaction

Button(title: str)
btn = cake.add(Button("Button"))
btn.clicked = lambda x: print(x)
btn.value = "My button"
Input(label: str, default=None, placeholder=None)
myinput = cake.add(Input("input", default=1))
myinput.value_changed = lambda x: print(x)
myinput.value += 1
DictInput(input_dict: dict)
dict_input = cake.add(DictInput({"a": 0, "b": 2}))
print(dict_input.value) # -> {"a": 0, "b": 2}
dict_input["a"] = 1
print(dict_input["a"]) # -> 1
dict_input.value_changed = cake.show_message
Slider(key: str, range_min: float, range_max: float, value: float=None)
slider = cake.add(Slider("myslider", range_min=0, range_max=10, value=5))
slider.value_changed = cake.show_message

Toppings for display

Label(text: str)
Text(text: str)
Paragraph(text: str)
ImageView(image: PIL.Image|str)
  • image argument should be PIL.Image or a path to a image file.
ImageCard(image: PIL.Image|str)
Table(df: pandas.DataFrame)

Toppings for arrangement

Row()
cake = Pancake()
row = cake.add(Row())
my_input = row.add(Input("Input"))
button = row.add(Button("Go!"))
button.clicked = lambda: cake.show_message(my_input.value)
Column()
cake = Pancake()
column = cake.add(Column())
my_input = column.add(Input("Input"))
button = column.add(Button("Go!"))
button.clicked = lambda: cake.show_message(my_input.value)
Card()

Make its content draggable such as ImageCard.

cake = Pancake()
card = cake.add(Card())
row = card.add(Row())
for key in ["a", "b", "c"]:
	row.add(Slider(key, 0, 10))
FloatingCard()

A Card topping floating above other toppings with a classic-style draggable handle.

Automated toppings

FromFunction(function)

Serving multiple pancakes

As you know, a pacanke should be served on a plate. That is,

from pancakekit import *

plate = Plate()

class Crape(Pancake):
	def decorate(self):
		b = self.add(Button("I want to have a waffle!"))
		b.clicked = lambda: plate.go_to("Waffle")
	def show_up(self):
		plate.cake["greediness"].value += 1
    
class Waffle(Pancake):
	def decorate(self):
		b = self.add(Button("I want to have a taiyaki!"))
		b.clicked = lambda: plate.go_to("Taiyaki")
	def show_up(self):
		plate.cake["greediness"].value += 2

class Taiyaki(Pancake):
	def decorate(self):
		b = self.add(Button("I want to have a crape!"))
		b.clicked = lambda: plate.go_to("Crape")
	def show_up(self):
		plate.cake["greediness"].value += 3

Crape(plate)
Waffle(plate)
Taiyaki(plate)
plate.cake.add(Label("Greediness:"))
plate.cake.add(0, name="greediness")
		
plate.serve()

When subclassing Pancake, describe the procedure to be performed once when instantiating in the decorate() and the procedure to be performed each time the page is displayed in show_up().

Making a custom topping

One of the simplest custom topping is as follows:

class MyLabel(Topping):
    def prepare(self, text):
        self.value = text
        style = {"text-shadow": f"1px 1px 1px #bbb"}
        self.label = Tag("label", style = style, value_ref=self)
        
    def html(self):
        return self.label.render()

prepare(self, *args, **kwargs) is called once when the topping is added to a pancake. html(self) returns a HTML string to be rendered.

Other than prepare and html, you can override the following methods:

  • value_getter(self) -> Any

    This method returns a value of the topping. The default behavior is to return self._value.

  • value_preprocessor(self, value: Any) ->Any

    This method processes value and returns a value to be stored in self._value. The default behavior is to return value.

  • web_value_preprocessor(self, tag: Tag, value: Any) ->Any

    This method processes value from the browser input and returns a value to be stored in self._value. The default behavior is to return value.

  • event_preprocessor(self, event: Event)->Any

    This value processes event and returns a value to be passed to an user-defined function. The default behavior is to return event.value.