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
ormongodb
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:
1 | pip3 install pymongo |
- Install the Kivy library as well:
1 | 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:
1 2 3 4 5 6 7 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 |
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:
1 2 3 | # 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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | # 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.
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:
1 2 3 4 5 | # 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:
1 2 3 4 5 6 7 8 9 10 11 | # 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:
1 2 3 4 5 | 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:
1 2 3 4 5 6 | # 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:
1 2 3 4 5 6 7 8 9 10 | # 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:
1 2 3 4 5 6 7 8 9 10 11 | # 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
1 2 3 4 5 6 7 8 9 10 | # 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:
1 2 3 4 5 6 | # 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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | # 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
1 2 3 4 5 6 7 8 9 | # 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:
1 2 3 4 5 6 7 8 | # 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:
1 2 3 4 5 6 7 8 | # 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:
1 2 3 4 5 6 7 8 9 | # 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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | # 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:
1 2 | # 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.
Just the Code
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 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 | #!/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