React-Redux Todo List Ported to Kivy

Tue 01 March 2016

Kivy and React.js share many similarities. Enough so that I was able to do an almost line for line translation of not just React.js code but also the Redux architecture that is one of the recommended ways of organizing a React.js app. This blog covers the code found in this repo, which is a direct port of the Redux example code.

Why Redux?

Like React.js, Kivy is mostly non-opinionated on how you organize your app, in fact even more so than React.js, which favors a one way flow of information; Kivy will allow you to use either one way or two way binding, it all depends on what you are comfortable with. However, with this flexibility comes confusion, we do not provide a lot of guidance on how to architect a Kivy application, and there are many possible ways to do so. Today I am going to talk about redux's architecture as it has some definitive benefits when it comes to organizing your application and is fairly easy to get started with. It is a very functional approach to handling your applications state, so be prepared for maps and filters and such thinking.

For more details, you cannot get more succint than the reasons behind Redux: Motivation for Redux and Three Principles of Redux.

The Setup

We are going to have 5 parts to our application:

  1. Presentational Components: these are simple UI widgets that do not have direct access to the app state. They are controlled by the container components.

  2. Container Components: These widgets link the simpler components to the state of the application.

  3. Actions: These objects describe how the state is being mutated.

  4. Reducers: These are pure functions that take the previous state of the app, and an Action object and return the new state of the application.

  5. Store: The store is responsible for connecting the Containers to the Reducers and holding the state of the app.

The basic loop for your application will look like this:

  1. Components generate an Action and call store.dispatch on it.

  2. The store runs the actiont through all reducers. A new state is returned and made the present state.

  3. The store maps the new state to all subscribed widgets.

  4. Repeat.

The Todo List App

We are going to make a Todo List application that will consist of 3 major widgets:

  1. At the top will be a text input and button for submitting a new Todo

  2. In the middle will be a scrolling list of all the recorded Todos, if a task is completed we will strikethrough the text.

  3. We will have a tray with 3 buttons that allows us to switch between all task, uncompleted task, and completed task views.

It will look like this:

TodoApp

Creating the Actions

Here's what the actions look like in Redux:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//actions/index.js
let nextTodoId = 0
export const addTodo = (text) => {
  return {
    type: 'ADD_TODO',
    id: nextTodoId++,
    text
  }
}

export const setVisibilityFilter = (filter) => {
  return {
    type: 'SET_VISIBILITY_FILTER',
    filter
  }
}

export const toggleTodo = (id) => {
  return {
    type: 'TOGGLE_TODO',
    id
  }
}

Just functions that return dicts with arbitrary data. This is a little nicer in JS because dicts automatically have . lookup syntax. We will create an Action object to emulate the look of the JS api better:

1
2
3
4
5
6
7
#actions/action.py
class Action(object):

def __init__(self, action_type, **kwargs):
    self.action_type = action_type
    for key in kwargs:
        setattr(self, key, kwargs[key])

Pretty simple, every action will have a type so we ensure that is there, but everything else is dynamically up to you. We also see one of the other major differences between js and python. With a much larger std lib we have a lot more builtins to avoid. So we break out good old hungarian notation.

Ok let's make our actions:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
#actions/__init__.py
from action import Action

next_todo_id = 0

def add_todo(text):
    global next_todo_id
    action = Action('ADD_TODO', t_id=next_todo_id, text=text)
    next_todo_id += 1
    return action

def set_visibility_filter(vis_filter):
    return Action('SET_VISIBILITY_FILTER', vis_filter=vis_filter)

def toggle_todo(t_id):
    return Action('TOGGLE_TODO', t_id=t_id)

So there we have the three actions that will govern our apps state:

  1. Add a new item to the todo list

  2. Set the filter for which type of items are visible.

  3. Toggle the completed status of a todo item.

Now onward to the reduers that process these actions into the state of the app.

Creating the Reducers

Reducers never modify the existing state, only returning a new state to replace the old. The first reducer handles the 'TOGGLE_TODO' and 'ADD_TODO' events. If a reducer does not modify the state it simply returns the previous state. Here is what the todo reducer looks like in js:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
//reducers/todos.js
const todo = (state, action) => {
  switch (action.type) {
    case 'ADD_TODO':
      return {
        id: action.id,
        text: action.text,
        completed: false
      }
    case 'TOGGLE_TODO':
      if (state.id !== action.id) {
        return state
      }

      return Object.assign({}, state, {
        completed: !state.completed
      })

    default:
      return state
  }
}

const todos = (state = [], action) => {
  switch (action.type) {
    case 'ADD_TODO':
      return [
        ...state,
        todo(undefined, action)
      ]
    case 'TOGGLE_TODO':
      return state.map(t =>
        todo(t, action)
      )
    default:
      return state
  }
}

export default todos

Here is it in Python:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#reducers/todo.py
from copy import deepcopy

def todo(state, action):
    if action.action_type == 'ADD_TODO':
        return {'t_id': action.t_id, 'text': action.text, 'completed': False}
    elif action.action_type == 'TOGGLE_TODO':
        if state['t_id'] != action.t_id:
            return state
        else:
            new_state = deepcopy(state)
            new_state.update({'completed': not state['completed']})
            return new_state
    else:
        return state


def todos(action, state=[]):
    if action.action_type == 'ADD_TODO':
        return state + [todo(state, action)]
    elif action.action_type == 'TOGGLE_TODO':
        return [todo(t, action) for t in state]
    else:
        return state

The first thing to note is that we've changed the order of the args from state, action to action, state so that we can use kwargs similar to the default args in js. In addition, we will use deepcopy and update to emulate the behavior of the js Object.assign function. Theoretically in many cases you could use a regular copy, but I've used deepcopy here to hopefully avoid any issues.

The second reducer handles the visibility filter state, in js it looks like:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
//reducers/visibilityFilter.js
const visibilityFilter = (state = 'SHOW_ALL', action) => {
  switch (action.type) {
    case 'SET_VISIBILITY_FILTER':
      return action.filter
    default:
      return state
  }
}

export default visibilityFilter

In python:

1
2
3
4
5
6
#reducers/visibility_filter
def visibility_filter(action, state='SHOW_ALL'):
    if action.action_type == 'SET_VISIBILITY_FILTER':
        return action.vis_filter
    else:
        return state

Finally, we have to bundle up all the reducers for our app so that we can tell the store about them.

In JS:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
//reduers/index.js
import { combineReducers } from 'redux'
import todos from './todos'
import visibilityFilter from './visibilityFilter'

const todoApp = combineReducers({
  todos,
  visibilityFilter
})

export default todoApp

In python:

1
2
3
4
5
#reducers/__init__.py
from todo import todos
from visibility_filter import visibility_filter

todo_reducers = {'todos': todos, 'visibility_filter': visibility_filter}

Creating the Store

This part is going to be the most significantly different from the JS code, on account of us having to build our own redux implementation.

For reference, using the store looks like this in JS:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
import React from 'react'
import { render } from 'react-dom'
import { Provider } from 'react-redux'
import { createStore } from 'redux'
import todoApp from './reducers'
import App from './components/App'

let store = createStore(todoApp)

render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('root')
)

We need to have 3 major parts for our simple Redux store:

  1. When creating our Store we pass in the combined reducers.

  2. A connect function that takes 3 args, the function to map state from store to widget, the function to map dispatching from widget to store, and the type of widget. This will return a function that creates the new widget that is bound to the store.

  3. A dispatch function that takes an action, gets the new store state from the reducers, and dispatches the changes calling the map state functions bound with the connect function.

My store works very similar to the Redux store, all of our state will be stored in a dictionary with key of reducer name, and value of whatever that particular reducers state is. So first let us setup the initial store:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
#store.py
class Store(object):
from actions.action import Action

def __init__(self, reducers):
    self.reducers = reducers
    self.state_map_callbacks = {}
    self.data_store = self.init_store()

def init_store(self):
    reducers = self.reducers
    action = Action(None)
    new_state = {}
    for key in reducers:
        new_state[key] = reducers[key](action)
    return new_state

We store the reducers for access later, create a dict to track the callbacks that map state to our widgets, and then initialize the store by calling the reducers with a empty Action and no previous store input so that we get the default data our of our reducers. We will save the current state of the store on the data_store attr.

The hard part is the connect endpoint. This needs to take a widget class and return a function that creates such a widget, but bound to our store appropriately. My implementation looks like:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
#store.py
def _connect(self, map_state, map_dispatch, widget):
    self.state_map_callbacks[widget] = map_state
    init_data = map_state(self.data_store, widget)
    dispatches = map_dispatch(self.dispatch, widget)
    events = dispatches.get('bind', {})
    assigns = dispatches.get('assign', {})
    for key in assigns:
        setattr(widget, key, assigns[key])
    widget.bind(**events)
    return widget

def connect(self, map_state, map_dispatch, widget_type):
    return lambda *args, **kwargs: self._connect(map_state,
                                                 map_dispatch,
                                                 widget_type(*args,
                                                             **kwargs)
                                                )

So the connect returns a function that acts just like a widget constructor but with a little extra. The real _connect takes an actual instance of widget, and first invokes the map_state callback on that widget so that we set the widget to the current state of the store.

The next part of _connect diverges most heavily from the Redux API. The way binding works in react is slightly different from Kivy, and so we ned to split what is one set of dispatch callbacks into 2. For objects in the 'bind' dict we will just call widget.bind and let kivy's event dispatching take over, but often in Kivy we also will pass a function in as an ObjectProperty and invoke it later in that widget or the child, we need to setattr and not .bind these types of use cases and so we also have an 'assign' dict.

Finally, we need to handle the dispatch(action) endpoint.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
#store.py
def update_store(self, action):
    reducers = self.reducers
    data_store = self.data_store
    new_state = {}
    for key in reducers:
        new_state[key] = reducers[key](action, data_store[key])
    return new_state

def map_state(self):
    state_map_callbacks = self.state_map_callbacks
    for widget in state_map_callbacks:
        new_data = state_map_callbacks[widget](self.data_store, widget)
        for key in new_data:
            setattr(widget, key, new_data[key])

def dispatch(self, action):
    self.data_store = self.update_store(action)
    self.map_state()

Whenever we call dispatch, we will first update the store, which just loops through every reducer and calls the reducing function with the action setting the new state. Finally, we call map_state which takes all the callbacks registered with connect and updates the individual widgets.

Finally, we will use the module namespace to make our store available to other code:

1
2
3
4
#store.py
from reducers import todo_reducers

store = Store(todo_reducers)

Presentation Components

Now we create a few custom widgets to mirror the basic react.js components.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
//components/Todo.js
import React, { PropTypes } from 'react'

const Todo = ({ onClick, completed, text }) => (
  <li
    onClick={onClick}
    style={{
      textDecoration: completed ? 'line-through' : 'none'
    }}
  >
    {text}
  </li>
)

Todo.propTypes = {
  onClick: PropTypes.func.isRequired,
  completed: PropTypes.bool.isRequired,
  text: PropTypes.string.isRequired
}

export default Todo

In Kivy the part accomplished by the html here is done in the KV lang so we split these files into 2:

1
2
3
4
5
6
7
8
9
#components/todo.py
from kivy.uix.label import Label
from kivy.properties import ObjectProperty, BooleanProperty, NumericProperty
from kivy.uix.behaviors import ButtonBehavior

class Todo(ButtonBehavior, Label):
    release_callback = ObjectProperty(None)
    completed = BooleanProperty(False)
    t_id = NumericProperty(None)

.

1
2
3
4
<Todo>:
    strikethrough: self.completed
    on_release: self.release_callback(self.t_id)
    color: (0., 0., 0., 1.)

I have changed the implementation a little here because it is easier to handle this type of construct by splitting the callback object and its args into 2 properties and updating either when needed in Kivy. This also lets us have a finer more declarative control over the invocation and its args. Keep in mind strikethrough is a new prop in 1.9.2 and only for the SDL2 text provider. You will need to checkout master for this part to work.

Now the TodoList container:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
//components/TodoList.js
import React, { PropTypes } from 'react'
import Todo from './Todo'

const TodoList = ({ todos, onTodoClick }) => (
  <ul>
    {todos.map(todo =>
      <Todo
        key={todo.id}
        {...todo}
        onClick={() => onTodoClick(todo.id)}
      />
    )}
  </ul>
)

TodoList.propTypes = {
  todos: PropTypes.arrayOf(PropTypes.shape({
    id: PropTypes.number.isRequired,
    completed: PropTypes.bool.isRequired,
    text: PropTypes.string.isRequired
  }).isRequired).isRequired,
  onTodoClick: PropTypes.func.isRequired
}

export default TodoList

This component should just make a Todo component for every member of the todos list. In Kivy we will also have to handle scrolling manually as we don't have that done for us the same as in the browser. We will make our widget a ScrollView, and populate a child layout with our content:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
#components/todolist.py
from kivy.properties import ListProperty, ObjectProperty
from kivy.uix.scrollview import ScrollView
from todo import Todo

class TodoList(ScrollView):
    todos = ListProperty([])
    item_callback = ObjectProperty(None)

    def on_todos(self, instance, value):
        container = self.ids.todos
        container.clear_widgets()
        for todo in value:
            container.add_widget(Todo(text=todo['text'], t_id=todo['t_id'],
                                      completed=todo['completed'],
                                      release_callback=self.item_callback,
                                      size_hint=(1.0, None), height=50))

.

1
2
3
4
5
6
<TodoList>:
    StackLayout:
        orientation: 'lr-tb'
        id: todos
        size_hint: (1., None)
        height: self.minimum_height

Now the Link button:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
//components/Link.js
import React, { PropTypes } from 'react'

const Link = ({ active, children, onClick }) => {
  if (active) {
    return <span>{children}</span>
  }

  return (
    <a href="#"
       onClick={e => {
         e.preventDefault()
         onClick()
       }}
    >
      {children}
    </a>
  )
}

Link.propTypes = {
  active: PropTypes.bool.isRequired,
  children: PropTypes.node.isRequired,
  onClick: PropTypes.func.isRequired
}

export default Link

Python:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
from kivy.uix.togglebutton import ToggleButton
from kivy.properties import ObjectProperty, StringProperty

class Link(ToggleButton):
    vis_filter = StringProperty(None)

    def on_vis_filter(self, instance, value):
        options = {'SHOW_ALL': 'All', 'SHOW_ACTIVE': 'Active',
                   'SHOW_COMPLETED': 'Completed'}
        self.text = options[value]

    def on_touch_down(self, touch):
        if self.state == 'down':
            return False
        else:
            super(Link, self).on_touch_down(touch)

In JS this widget gets told it's human readable text during the declaration, I've made our Kivy widget update automatically since I think it's a little cleaner (and we aren't stuck declaring widgets in HTML!). I've also made our widget reject touches if its already down, so that we don't update our state to what it already is. There is no KV for this widget because it does not need any styling.

Container Components

With the basic widgets created, we are now ready to use our Store.connect function to create widgets linked to our Store. This will involve declaring the functions that map state to the widget and dispatch widget events to the store.

In JS this looks like:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
//containers/VisibleTodoList.js
import { connect } from 'react-redux'
import { toggleTodo } from '../actions'
import TodoList from '../components/TodoList'

const getVisibleTodos = (todos, filter) => {
  switch (filter) {
    case 'SHOW_ALL':
      return todos
    case 'SHOW_COMPLETED':
      return todos.filter(t => t.completed)
    case 'SHOW_ACTIVE':
      return todos.filter(t => !t.completed)
  }
}

const mapStateToProps = (state) => {
  return {
    todos: getVisibleTodos(state.todos, state.visibilityFilter)
  }
}

const mapDispatchToProps = (dispatch) => {
  return {
    onTodoClick: (id) => {
      dispatch(toggleTodo(id))
    }
  }
}

const VisibleTodoList = connect(
  mapStateToProps,
  mapDispatchToProps
)(TodoList)

export default VisibleTodoList

In Python:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#containers/visibletodolist.py
from components import TodoList
from store import store
from kivy.factory import Factory
from actions import toggle_todo

def get_visible_todos(todos, vis_filter):
    if vis_filter == 'SHOW_ALL':
        return todos
    elif vis_filter == 'SHOW_COMPLETED':
        return filter(lambda x: x['completed'], todos)
    elif vis_filter == 'SHOW_ACTIVE':
        return filter(lambda x: not x['completed'], todos)

def map_state_to_props(state, widget):
    return {'todos': get_visible_todos(state['todos'], 
                                       state['visibility_filter'])}

def map_dispatch_to_props(dispatch, widget):
    return {'assign': {
                'item_callback': lambda t_id: dispatch(toggle_todo(t_id))
                }
        }

VisibleTodoList = store.connect(map_state_to_props, map_dispatch_to_props,
                                TodoList)
Factory.register('VisibleTodoList', VisibleTodoList)

The functions themselves are almost the same, with the exception of the previously mentioned split of the dispatch returns into 2 categories of 'assign' and 'bind'. Finally we need to Factory.register the widget we have connect on so that we can use our new widget in KV.

Let's do the same for the Link widget:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
//containers/FilterLink.js
import { connect } from 'react-redux'
import { setVisibilityFilter } from '../actions'
import Link from '../components/Link'

const mapStateToProps = (state, ownProps) => {
  return {
    active: ownProps.filter === state.visibilityFilter
  }
}

const mapDispatchToProps = (dispatch, ownProps) => {
  return {
    onClick: () => {
      dispatch(setVisibilityFilter(ownProps.filter))
    }
  }
}

const FilterLink = connect(
  mapStateToProps,
  mapDispatchToProps
)(Link)

export default FilterLink

In Python:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
#containers/filterlink.py
from components import Link
from actions import set_visibility_filter as set_vis_fil
from store import store
from kivy.factory import Factory

def map_state_to_props(state, widget):
    if widget.vis_filter == state['visibility_filter']:
        return {'state': 'down'}
    else:
        return {'state': 'normal'}

def map_dispatch_to_props(dispatch, widget):
    return {'bind': {
                'on_release': lambda i: dispatch(
                                        set_vis_fil(widget.vis_filter)),
                }
            }


FilterLink = store.connect(map_state_to_props, map_dispatch_to_props, Link)
Factory.register('FilterLink', FilterLink)

This example uses the 'bind' dispatchers, and if you only do things like this you will stay much closer to pure Redux. You just need to have the added closure over your lambda to access the actual widget properties this way.

Finally, we can also declare a widget that dispatches to the store without actually calling connect.

In JS:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
//containers/AddTodo.js
import React from 'react'
import { connect } from 'react-redux'
import { addTodo } from '../actions'

let AddTodo = ({ dispatch }) => {
  let input

  return (
    <div>
      <form onSubmit={e => {
        e.preventDefault()
        if (!input.value.trim()) {
          return
        }
        dispatch(addTodo(input.value))
        input.value = ''
      }}>
        <input ref={node => {
          input = node
        }} />
        <button type="submit">
          Add Todo
        </button>
      </form>
    </div>
  )
}
AddTodo = connect()(AddTodo)

export default AddTodo

In Python:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
containers/addtodo.py
from kivy.uix.boxlayout import BoxLayout
from store import store
from actions import add_todo

class AddTodo(BoxLayout):

    def add_todo_callback(self):
        text_input = self.ids.text_input
        store.dispatch(add_todo(text_input.text))
        text_input.text = ''

.

1
2
3
4
5
6
7
8
9
<AddTodo>:
    orientation: 'horizontal'
    TextInput:
        id: text_input
        size_hint: (.7, 1.0)
    Button:
        text: 'Submit'
        size_hint: (.3, 1.0)
        on_release: root.add_todo_callback()

We just need to call dispatch diretly after creating our action object. Note this is only useful for components that do not need to receive state back from the store, only modify it.

Using A connected Widget in Kivy

Ok one final widget to create and then we put everything together. This component will make use of containers we have connected, in this case the FilterLink.

In JS:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//components/Footer.js
import React from 'react'
import FilterLink from '../containers/FilterLink'

const Footer = () => (
  <p>
    Show:
    {" "}
    <FilterLink filter="SHOW_ALL">
      All
    </FilterLink>
    {", "}
    <FilterLink filter="SHOW_ACTIVE">
      Active
    </FilterLink>
    {", "}
    <FilterLink filter="SHOW_COMPLETED">
      Completed
    </FilterLink>
  </p>
)

export default Footer

In Python:

1
2
3
4
5
#components/footer.py
from kivy.uix.boxlayout import BoxLayout

class Footer(BoxLayout):
    pass

.

1
2
3
4
5
6
7
8
<Footer>:
    orientation: 'horizontal'
    FilterLink:
        vis_filter: 'SHOW_ALL'
    FilterLink:
        vis_filter: 'SHOW_ACTIVE'
    FilterLink
        vis_filter: 'SHOW_COMPLETED'

It works just like a normal widget, but will automatically connect to our app state!

Assemble the App

The final step is to take our 3 major widgets: The AddTodo, Footer, and VisibleTodoList widgets and arrange them into our actual app.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
//components/App.js
import React from 'react'
import Footer from './Footer'
import AddTodo from '../containers/AddTodo'
import VisibleTodoList from '../containers/VisibleTodoList'

const App = () => (
  <div>
    <AddTodo />
    <VisibleTodoList />
    <Footer />
  </div>
)

export default App

In Kivy we will do this by just returning a root widget in our kv file

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
import kivy
from kivy.app import App
from store import store
import containers


class ToDoApp(App):

    def build(self):
        pass


if __name__ == '__main__':
    ToDoApp().run()

.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
##todo.kv
BoxLayout:
    orientation: 'vertical'
    canvas.before:
        Color:
            rgb: (1., 1., 1.)
        Rectangle:
            size: self.size
            pos: self.pos
    AddTodo:
        size_hint: (1., .15)
    VisibleTodoList:
        size_hint: (1., .75)
    Footer:
        size_hint: (1., .1)

I've added a white background so it is more similar to a web client as Kivy defaults to black usually.

Well there you have it, Redux in Kivy.

blogroll