Build A MongoDB GUI App Using Kivy And Python (Part 2)

Introduction

This article demonstrates how to build a GUI application for MongoDB, that can be deployed to multiple platforms, using the Kivy framework and Python programming language in less than 200 lines of code. The completed application in this article will allow the user to access the databases on a MongoDB localhost server, and retrieve their respective collection names, by making PyMongo API calls inside of the Kivy App class.

This is Part 2 of a two-part series. The last part showed how to install the libraries, connect to the MongoDB server, and finally setup the Kivy application class. This article will show how to create button and label widgets for the app, as well how to make API calls to MongoDB, using PyMongo, and then use the returned data to change the Kivy label widgets.

Prerequisites for creating a MongoDB GUI application with Python and Kivy

  • It’s recommended that you use Python 3 for the example code in this article. The script has not been tested on Python 2, and Python 2.7 is losing support and is now deprecated.

  • The MongoDB server should be running on your localhost on the same machine that will execute the Python script for the Kivy application. Use the mongod or mongodb command, in a terminal or command prompt window, to ensure that the server is running.

  • Install the PyMongo distribution using the pip3 command if you haven’t done so already:

  • The first part of the series demonstrated how to install the Python packages for the MongoDB app. Here are the PIP3 commands once again to install the Kivy and MongoDB libraries for Python:

pip3 install pymongo
  • Install the Kivy library as well:
pip3 install

Create a Python script and import the package libraries for PyMongo and Kivy

Create a new directory for the Kivy application files, and, in the app’s Python script, make sure to include the following code to import the necessary package libraries for the MongoDB GUI application:

# import the necessary Kivy libraries
from kivy.app import App
from kivy.uix.button import Button
from kivy.uix.label import Label
from kivy.uix.boxlayout import BoxLayout

# import the MongoClient class
from pymongo import MongoClient, errors

The examples used in the next few sections were given in the first installment of the series, so the following bit of code is a refresher in case you missed it.

Check that the MongoDB server is running using PyMongo

The following is some of the code from the first article showing how one can check the PyMongo connection to MongoDB while instantiating a client instance of the library.

Declare the MongoDB domain and port variables for the client’s ‘host’

In the first part we demonstrated how to pass some values to the MongoClient() method’s host parameter.

Declare the following objects outside of any class or function declaration to give the variables a global scope:

# global variables for MongoDB host (default port is 27017)
DOMAIN = 'localhost:'
PORT = 27017

Declaring an instance of the MongoClient() method library for PyMongo

This code uses a try-except indentation block to declare an instance of the MongoClient() library. If there’s an error, or a problem connecting to MongoDB using the specified domain and port, then it will return a NoneType object instead of the MongoClient object:

# use a try-except indentation to catch MongoClient() errors
try:
    # try to instantiate a client instance
    client = MongoClient(
        host = [ str(DOMAIN) + str(PORT) ],
        serverSelectionTimeoutMS = 3000 # 3 second timeout
    )

    # print the version of MongoDB server if connection successful
    print ("server version:", client.server_info()["version"])

    # get the database_names from the MongoClient()
    database_names = MongoClient().list_database_names()

except errors.ServerSelectionTimeoutError as err:
    # set the client and DB name list to 'None' and `[]` if exception
    client = None
    database_names = []

    # catch pymongo.errors.ServerSelectionTimeoutError
    print ("pymongo ERROR:", err)

Unlike the first article, however, the code above creates a global list object containing all of the MongoDB server’s databases, but will return an empty list if the connection failed, or if the API call, for whatever reason, returns a ServerSelectionTimeoutError exception.

The list of database names will be used to populate a list of Kivy Button() widgets later on.

Screenshot of a Kivy app displaying a failed MongoDB connection data as text in label widgets

The example code in this article uses a 3 seconds (serverSelectionTimeoutMS = 3000) timeout to attempt to connect before raising an exception and displaying the connection error in the Kivy app.

Declare a Kivy App Python class for the MongoDB GUI application

Declare a new class with Python’s class keyword for the Kivy application by passing the entire App library to the class declaration as an argument so that it can inherit all of Kivy’s classes and methods:

# create a new class for the Kivy MongoDB app
class MongoApp(App):

    # define the build() function for the app
    def build(self):

All of the Kivy widget declarations and events will be inside of this build() function for the application.

Assign new values to the Kivy App’s class attributes

Classes in Python have a “self” keyword that serves as an instance, or a stand-in, for the class object itself. The following example code uses self to access the Kivy App class attributes.

This application uses the "vertical" layout which “stacks” the widgets vertically, on top of one another, as each one is added with the add_widget() method call, and the following code assigns a string value to the class’s title attribute to give the application window a new title:

        # change the app's attributes
        self.title = 'ObjectRocket MongoDB App'

        # declare a new object for the selected Mongo collection
        self.selected_db = None # None selected by default

        # concatenate the host's domain and port variables
        self.mongo_domain = str(DOMAIN) + str(PORT)

        # set the layout for the Kivy aYou can change the title for the application by pplication
        self.layout = BoxLayout(orientation='vertical')

NOTE: The selected_db attribute doesn’t actually exist as a part of the App class, but we’ll declare it in order to keep track of which database was selected by the user.

Create a Kivy label widget for the MongoDB client status

Declare a new Label() instance for the domain name information, and also a label that displays the selected MongoDB database:

        domain_label = Label(font_size=50)
        self.layout.add_widget(domain_label)

        db_label = Label(font_size=40)
        self.layout.add_widget(db_label)

NOTE: Make sure to pass each Label() instance to the layout object’s add_widget() method in the order that you’d like each widget to appear in the application window.

Evaluate the MongoDB client instance and create Kivy widgets for the database names

Check if the PyMongo MongoClient() method was able to return a valid client instance (instead of just None), and iterate over the list of the server’s database names using Python’s enumerate() function:

        # eval connection to MongoDB with global client instance
        if client != None:

            # enumerate the list of database names to create Kivy widgets
            for num, index in enumerate(database_names):
                print ("\nMongoDB db:", num, '--', index)

Append the Kivy Button() widget with each database iteration

Make sure to append a new Kivy Button() widget in each iteration, and then access the last widget in the list using the [-1] bracket pointer:

                # append the new DB button to the list
                button_list += [Button()]

                # get the latest button in list
                last_button = button_list[-1]

                # change the new button's properties
                last_button.padding = (25, 0)
                last_button.text = str(index)
                last_button.font_size = 34

Once you’ve accessed the widget you can then change its padding, text, and font_size properties.

Use Python’s lambda function to change the Kivy widgets’ text

Python has a built-in function called lambda that let’s you declare a function while assigning some value to an object. Use lamda to pass some variables to each widget’s callback function (to be declared later on).

Make sure to pass the App class’s self keyword, the MongoDB database button’s text, and the database label widget to the function call:

                # use Python's lamda feat to pass a func to button's on_press()
                new_db_name = last_button.bind(
                    on_press = lambda last_button:
                        self.db_callback(self, last_button.text, db_label)
                )

                # add the widget to the layout at the end of iteration
                self.layout.add_widget(last_button)

            # print the final list after the loop is complete
            print ("\nbutton_list:", button_list)

Add each widget to the layout at the end of each iteration in the loop.

Change the Kivy labels’ text to reflect the returned MongoDB data

Evaluate the selected_db attribute, that we declared earlier, to see if the user has pressed a MongoDB database button

        # change the text for the label if DB is selected
        if self.selected_db != None:
            db_label.text = str(new_db_name)

        # if a database button has NOT been clicked yet
        else:
            db_label.text = "Please select a database"

        # change the label to reflect the MongoDB client's 'host'
        domain_label.text = self.mongo_domain

The above code will change the domain label’s text attribute to reflect the host parameters passed earlier.

Declare a Kivy label widget for the MongoDB collections

The last widget to display MongoDB information is for the collection data returned by PyMongo after a user presses a database button:

        # create label for the collections
        self.col_label = Label(
            font_size = 26,
            padding = (20, 20),
            color = (0.15, 0.3, 1, 1) # last int is alpha channel
        )

NOTE: Kivy uses 4 float values in a tuple array to determine colors for widgets with 1.0 being the highest value (equivalent to 255 in the RGBA color scale), and 0.0 representing black, or full transparency for the alpha channel.

Change the Kivy labels after evaluating the PyMongo client instance

Change the Kivy labels based on the PyMongo client’s values. The Kivy app will display red text if PyMongo failed to connect to the MongoDB server, but it will be blue if the connection was successful:

        # eval connection to MongoDB with global client instance
        if client != None:
            domain_label.text = str(self.mongo_domain)
            db_label.text = "Select a MongoDB database"

            # change font color to blue
            self.col_label.color = (0.2, 0.2, 1, 1) # 4th ele is alpha channel

        # display warning message if NOT connected to MongoDB
        else:
            domain_label.text = "ERROR: Not connected to MongoDB"

            # inform the user if the connection to MongoDB failed
            db_label.text = "Invalid Host parameters,"
            db_label.text += "\nor the MongoDB server isn't running."

            # change font color to red if connection failed
            domain_label.color = (1, 0, 0, 1) # red color font
            self.col_label.color = (1, 0, 0, 1) # red color font

Give a warning using the collection label widget if MongoDB failed to connect

        # client param invalid or MongoDB isn't running
        if client == None:
            self.col_label.text = "Your client's host parameters are invalid,"
            self.col_label.text += "\nor your MongoDB server isn't running."

        # PyMongo client IS connected to MongoDB
        else:
            self.col_label.text = "You're connected to:" + self.mongo_domain
            self.col_label.text += "\nClick a database button to find its collections."

Add the widget labels to the Kivy layout

Add the widget labels to the Kivy layout and return the layout after their data has been altered to reflect the data returned by the PyMongo API calls to the MongoDB server:

        # add the collection info label widgets to the layout
        self.layout.add_widget(self.col_label)

        # add a "padding" widget for extra space at the bottom
        self.layout.add_widget(Label(padding=(50, 50)))

        # return the layout at the end of build() func
        return self.layout

The Kivy add_widget() method call adds the labels to the layout in the order that each one is called.

Declare the Kivy callback function for the MongoDB database buttons

Use Python’s def keyword to declare a callback function, inside of the Kivy App class indentation, for the MongoDB databaseButton() widgets that will change the text attribute for Kivy widgets:

    # declare a callback func for the database buttons
    def db_callback(self, event, text, label):

        # change the text for the selected DB label
        self.selected_db = text

        # change the label for the button
        label.text = text

Use PyMongo to make an API call to MongoDB to get the database’s collection names

Evaluate the client instance once more in the callback function and make an API to MongoDB to get the selected database’s names in a list object:

        # if the PyMongo client IS connected to MongoDB
        if client != None:

            # get the collection names for the selected Mongo DB
            collection_names = client[self.selected_db].list_collection_names()
            print ("collection_names:", collection_names)

            # reset the string for the collection names
            new_text = ""

Declare an empty string that will be used to display all of the collection names in a Kivy Label() widget.

Add the collection names to the Kivy label’s text if connected to MongoDB

Use enumerate() to loop over the MongoDB collection names and append each name to the string for the label widget:

            # enumerate the list of collection names
            for num, col in enumerate(collection_names):

                # append the collection name to the new label string
                new_text += str(col)

                # add some delimiter hyphens if there many collections
                if num+1 < len(collection_names):
                    new_text += " -- "

            # change the label's text to reflect final string
            self.col_label.text = new_text

        # if the PyMongo client is NOT connected to a MongoDB server
        else:
            # print the invalid 'host' parameter in the terminal
            print ("Python client instance is invalid:", self.mongo_domain)

NOTE: The above code uses two hyphens ( --) to delimit each collection in order to save some vertical real estate space within the app.

Call the Kivy app’s run() method

Call the Kivy app’s run() method at the end of the Python script that will run the application when the Python script is executed:

# run the MongoDB Kivy app class
MongoApp().run()

Conclusion

Perhaps Kivy’s best feature, besides its ease of use, is its ability to easily deploy apps on multiple platforms. For the sake of brevity this example application has only limited functionality (especially when compared to Mongo’s Compass UI application), but hopefully, after reading this article, you can see the potential in using Kivy to deploy your own customized GUI application to multiple platforms.

Gif of the MongoDB GUI application created using Kivy and Python

Just the Code

#!/usr/bin/env python3
#-*- coding: utf-8 -*-

# import the necessary Kivy libraries
from kivy.app import App
from kivy.uix.button import Button
from kivy.uix.label import Label
from kivy.uix.boxlayout import BoxLayout

# import the MongoClient class
from pymongo import MongoClient, errors

# global variables for MongoDB host (default port is 27017)
DOMAIN = 'localhost:'
PORT = 27017

# use a try-except indentation to catch MongoClient() errors
try:
    # try to instantiate a client instance
    client = MongoClient(
        host = [ str(DOMAIN) + str(PORT) ],
        serverSelectionTimeoutMS = 3000 # 3 second timeout
    )

    # print the version of MongoDB server if connection successful
    print ("server version:", client.server_info()["version"])

    # get the database_names from the MongoClient()
    database_names = MongoClient().list_database_names()

except errors.ServerSelectionTimeoutError as err:
    # set the client and DB name list to 'None' and `[]` if exception
    client = None
    database_names = []

    # catch pymongo.errors.ServerSelectionTimeoutError
    print ("pymongo ERROR:", err)

# create a new class for the Kivy MongoDB app
class MongoApp(App):

    # define the build() function for the app
    def build(self):

        # change the app's attributes
        self.title = 'ObjectRocket MongoDB App'

        # declare a new object for the selected Mongo collection
        self.selected_db = None # None selected by default

        # concatenate the host's domain and port variables
        self.mongo_domain = str(DOMAIN) + str(PORT)

        # set the layout for the Kivy application
        self.layout = BoxLayout(orientation='vertical')

        # instantiate button without eval() by putting them in a list
        button_list = []
        label_list = []

        domain_label = Label(font_size=50)
        self.layout.add_widget(domain_label)

        db_label = Label(font_size=40)
        self.layout.add_widget(db_label)

        # eval connection to MongoDB with global client instance
        if client != None:

            # enumerate the list of database names to create Kivy widgets
            for num, index in enumerate(database_names):
                print ("\nMongoDB db:", num, '--', index)

                # append the new DB button to the list
                button_list += [Button()]

                # get the latest button in list
                last_button = button_list[-1]

                # change the new button's properties
                last_button.padding = (25, 0)
                last_button.text = str(index)
                last_button.font_size = 34

                # use Python's lamda feat to pass a func to button's on_press()
                new_db_name = last_button.bind(
                    on_press = lambda last_button:
                        self.db_callback(self, last_button.text, db_label)
                )

                # add the widget to the layout at the end of iteration
                self.layout.add_widget(last_button)

            # print the final list after the loop is complete
            print ("\nbutton_list:", button_list)

        # change the text for the label if DB is selected
        if self.selected_db != None:
            db_label.text = str(new_db_name)

        # if a database button has NOT been clicked yet
        else:
            db_label.text = "Please select a database"

        # change the label to reflect the MongoDB client's 'host'
        domain_label.text = self.mongo_domain

        # create label for the collections
        self.col_label = Label(
            font_size = 26,
            padding = (20, 20),
            color = (0.15, 0.3, 1, 1) # last int is alpha channel
        )

        # eval connection to MongoDB with global client instance
        if client != None:
            domain_label.text = str(self.mongo_domain)
            db_label.text = "Select a MongoDB database"

            # change font color to blue
            self.col_label.color = (0.2, 0.2, 1, 1) # 4th ele is alpha channel

        # display warning message if NOT connected to MongoDB
        else:
            domain_label.text = "ERROR: Not connected to MongoDB"

            # inform the user if the connection to MongoDB failed
            db_label.text = "Invalid Host parameters,"
            db_label.text += "\nor the MongoDB server isn't running."

            # change font color to red if connection failed
            domain_label.color = (1, 0, 0, 1) # red color font
            self.col_label.color = (1, 0, 0, 1) # red color font

        # client param invalid or MongoDB isn't running
        if client == None:
            self.col_label.text = "Your client's host parameters are invalid,"
            self.col_label.text += "\nor your MongoDB server isn't running."

        # PyMongo client IS connected to MongoDB
        else:
            self.col_label.text = "You're connected to:" + self.mongo_domain
            self.col_label.text += "\nClick a database button to find its collections."

        # add the collection info label widgets to the layout
        self.layout.add_widget(self.col_label)

        # add a "padding" widget for extra space at the bottom
        self.layout.add_widget(Label(padding=(50, 50)))

        # return the layout at the end of build() func
        return self.layout

    # declare a callback func for the database buttons
    def db_callback(self, event, text, label):

        # change the text for the selected DB label
        self.selected_db = text

        # change the label for the button
        label.text = text

        # if the PyMongo client IS connected to MongoDB
        if client != None:

            # get the collection names for the selected Mongo DB
            collection_names = client[self.selected_db].list_collection_names()
            print ("collection_names:", collection_names)

            # reset the string for the collection names
            new_text = ""

            # enumerate the list of collection names
            for num, col in enumerate(collection_names):

                # append the collection name to the new label string
                new_text += str(col)

                # add some delimiter hyphens if there many collections
                if num+1 < len(collection_names):
                    new_text += " -- "

            # change the label's text to reflect final string
            self.col_label.text = new_text

        # if the PyMongo client is NOT connected to a MongoDB server
        else:
            # print the invalid 'host' parameter in the terminal
            print ("Python client instance is invalid:", self.mongo_domain)

# run the MongoDB Kivy app class
MongoApp().run()

Pilot the ObjectRocket Platform Free!

Try Fully-Managed CockroachDB, Elasticsearch, MongoDB, PostgreSQL (Beta) or Redis.

Get Started

Keep in the know!

Subscribe to our emails and we’ll let you know what’s going on at ObjectRocket. We hate spam and make it easy to unsubscribe.