Python Bottle Framework Basics

Python offers several popular web frameworks such as Django, TurboGears, Flask, and Pyramid, to name a few. In this post I want to take a look at the Bottle framework. Actually, I’ll be spending a couple of posts using Bottle before jumping into Django.

Bottle

From the Bottle website, Bottle is described as:

Bottle is a fast, simple and lightweight WSGI micro web-framework for Python.

Fast… great. Simple… awesome. Lighweight… sounds good. WSGI??? What is that? WSGI is an acronym for Web Server Gateway Interface and comes from the PEP 333 standard. Which, in a nutshell, means that Bottle is going to be compliant with and capable of supporting all (or most) interactions between a Web server and a Web framework. Cool, that sounds reasonable.

Now what about that last part, the micro web-framework? Well, that typically means that the web-framework isn’t as full features as an enterprise level framework, such as Django, and by default doesn’t include some features expected in a full fledged web framework. While Bottle can indeed be deployed to a server and have full functioning applications, I find Bottle, and other micro web-frameworks, to be great for prototyping of ideas.

What I’d like to build out as a prototype in Bottle, then switch it over to Flask, is a very basic blood sugar tracking and logging application for those dealing with Type I Diabetes. We’ll get more into the data that we need to keep track of and the database schema in another post. In this post, we will concern ourselves simply with getting familiar with Bottle, routes, templates, and static files.

Routing

As stated in Bottle’s project description, it is fast and simple to get things running. We, of course, need to install it first using

pip install bottle

At the time of this writing, the current version is 0.12.13.

I’m going to assume for this discussion that HTTP methods such as GET and POST are something with which you are familiar. If not you can see an explanation of them here.  For starters we need to see how to establish a basic route in Bottle, so let’s do one for the every popular “Hello World!” example.

app.py

import bottle

# Site Index
@bottle.route('/')
def index():
    return "Hello World!"

if __name__ == '__main__':
    bottle.run('localhost', 8082, debug=True, reloader=True)

Now, if we run our application and go to http://localhost:8082 on our local machine we should see an HTML page with the text “Hello World!”. As promised, pretty quick and easy to get a page up and running with Bottle. It would be great if we could return some generated information as well. What about just adding a name to our route and have that name displayed on the screen? We’ll use the cgi module, or Common Gateway Interface to obtain that information from our URL.

app.py

import bottle
import cgi


# Site Index
@bottle.route('/')
def index():
    return "Hello World!"

# Index with name
@bottle.route('/<name>')
def get_name(name="Nobody"):
    name = cgi.html.escape(name)
    return "Hello {}".format(name)


if __name__ == '__main__':
    bottle.run('localhost', 8082, debug=True, reloader=True)

This is pretty cool and super simple to get pages out and displayed on a web page, but they don’t look great and just returning strings is still pretty limiting.

Template Engine

Fortunately Bottle comes with and utilizes a basic template engine called SimpleTemplate. This will allow us to write HTML files, use CSS, and include Python code inside our templates. Our template files will be of the file type .tpl. Let’s see about changing our name route to use a template and pass the template our URL information. Let’s call our template name.tpl.

app.py

import bottle
import cgi


# Site Index
@bottle.route('/')
def index():
    return "Hello World!"

# Index with name
@bottle.route('/<name>')
def get_name(name="Nobody"):
    name = cgi.html.escape(name)
    return bottle.template('name.tpl', name=name)


if __name__ == '__main__':
    bottle.run('localhost', 8082, debug=True, reloader=True)

name.tpl

<!DOCTYPE html>
<html>
    <head>
        <title>Name Page</title>
    </head>

    <body>
        Hello {{name}}!
    </body>
</html>

Great, we are passing our name variable information from our route in app.py to our template and, using the {{...}} syntax we are able to capture and display information. We can pass many different arguments into the return statement of our route such as data coming back from a database, data for regarding errors, user names, page titles, etc.

One thing that is always nice to be able to do in a web framework is to not have to repeat ourselves in every template file for things such as the HTML head information and page footer information. Once again, Bottle and SimpleTemplate makes this easy with the % include() feature. Let’s see this in action by creating a header.tpl file, include it inside our name.tpl file, and pass in title data from our route.

header.tpl

    
<!DOCTYPE html>
<html>
    <head>
        <title>{{title or 'No title'}}</title>
    </head>

    <body>   

name.tpl

% include('header.tpl', title=title)

        Hello {{name}}!
    </body>
</html>

And let’s not forget to update our route information as well…

app.py Snippet

# Index with name
@bottle.route('/<name>')
def get_name(name="Nobody"):
    name = cgi.html.escape(name)
    return bottle.template('name.tpl', name=name, title="Name Page")

Fantastic! Now we can see how we can utilize information from our Python code in app.py and display it in our templates.

Static Files

One other think I would like to mention in this post is how to setup Bottle for including static files like CSS. Again, Bottle makes this easy by simply having us define the path to our static files and by importing static_file from bottle and returning a static_file. This is the recommended way to serve static files and it provides some extra security protection to the files. We can define a new route for static CSS files like so:

from bottle import static_file

# Static CSS Files
@bottle.route('/static/css/<filename:re:.*\.css>')
def send_css(filename):
    return static_file(filename, root='static/css')

With the exception of the regular expression filter in the link name, that should look pretty familiar. The regular expression is simply telling Bottle that any file name with a .css extension should be looked for, in this case, in the static/css directory.

Now, inside our header.tpl file we can include a <link type="text/css" href="/static/css/styles.css" rel="stylesheet"> line and have our CSS files safely included and if they are missing Bottle will provide some convenient error messages.

Wrap Up

I think that is a pretty decent start for our knowledge of Bottle, how to create some routes in general, some basic templates, and serving some static files. In another post, I’ll show a basic site that will have the necessary routes and templates in place for our Blood Sugar Tracker application. From there I’ll show how we can utilize MongoDB as a data store for the application and include session and user signup information so we can provide some basic authentication with sessions and cookies, which Bottle provides some great and amazingly simple tools to enable that functionality.

See you next time and Happy Coding!


Follow me on Twitter @kenwalger to get the latest updates. If you enjoyed this article, or have questions, leave comments below.

Facebooktwitterredditlinkedinmail

MongoDB CRUD with Python

In my last post, we looked at how to combat Impostor Syndrome and came to the conclusion that deepening our knowledge on a given topic helps to sharpen of knowledge sword to fight our feelings of self doubt. If you missed that post, take a look here. This week let’s take a look at using the NoSQL database MongoDB with Python. In a related post, I’ll show how to implement them into web frameworks.

Planning

We will start with an exploration of CRUD operations in MongoDB using Python and PyMongo. Next, we’ll have a look at the Python web framework Bottle and then use Bottle with MongoDB to generate a simple web site. Then we’ll spend a bit of time using MongoDB with the Django web framework to generate a slightly more involved site than our Bottle version.

In this post, we kick off this series with a simple discussion of how to do some simple CRUD operations in Python similar to my post for doing CRUD operations in Java. Not familiar with CRUD? It stands for Create, Read, Update, and Delete. Those are the basic operations one will find themselves doing with any database, not just MongoDB.

In December of 2016, MongoDB released a new version of their database, 3.4, and I’m using 3.4.1 for this series of posts. While we aren’t going to go into any release specific features this week, we might in future posts, so I would encourage you to utilize the latest version. We will be touching on some of the features in the latest version of Python, 3.6, later on as well so make sure you are utilizing that version as well.

Python Driver – PyMongo

Okay, let’s concentrate on some basic implementations and general syntax of the Python driver for MongoDB as well as some basic database documents. Documents, by the way, are similar to a SQL database record.

The project code for this post will be available on GitHub and a link will be included at the end of the post. Let’s start off with our requirements.txt file to list out our project dependencies:

pymongo=='3.4.0'

With that in place we can do pip install -r requirements.txt to install the required version of the libraries for our project.

Another thing we will need for our project to work is a running instance of a MongoDB server. I am using version 3.4.1 and will assume that you have MongoDB 3.4 downloaded, installed, and running on the default address of localhost and port 27017. Further, since we are just sharpening our sword of knowledge here and don’t need MongoDB server authentication, let’s assume that feature is turned off or not implemented at all on our server.

Great, now that our environment is setup let’s enter into the planning stages a bit. What do we need to do?

  • Connect to the database
  • Insert an individual document into the database (Create)
  • Find and print a document in the database (Read)
  • Update the document in the database
  • Finally, Delete the document from the database

Let’s get started!

Database Connection

To setup our connection we will need to import MongoClient from pymongo, set the host and port we intend on using, and then select the database and collection we need. Remember that in MongoDB a collection is similar to a table in the SQL world in that it is a namespace for our data.

from pymongo import MongoClient

HOST = 'localhost'
PORT = 27017

client = MongoClient(HOST, PORT)
database = client.cookbook
recipes = database.recipes

Great, we now have a connection to the MongoDB database cookbook and the collection recipes. But wait, we didn’t ask to specifically create either of those, nor did we put anything into the collection yet. That’s correct, MongoDB generates that for us and waits for us to start using cookbook.recipes. Now, we need to add a basic recipe to our collection so that we have something with which to work.

We’ll define a single recipe document and insert it into our collection using the insert_one() method. Here‘s a link to further documentation on PyMongo’s collection level operations.

recipe = {'title': 'chocolate milk',
          'description': 'Yummy drink',
          'ingredients': [
              {'name': 'milk', 'quantity': 8, 'unit of measure': 'ounce'},
              {'name': 'chocolate syrup', 'quantity': 2, 'unit of measure': 'ounce'}
          ],
          'yield': {'quantity': 1, 'unit': 'glass'},
          'prep time': 0,
          'cook time': 0,
          'author': 'Biff Tannen',
          'uploaded_by': 'kenwalger',
          }

recipes.insert_one(recipe)

That’s all there is to it. We have just added a Chocolate Milk recipe to our collection. We just call the insert_one() method on our collection and pass in our document as an argument.

Reading the documents

Getting a document back out is pretty straight forward as well, we can simply use the find_one() function, which is a collection level operator from the PyMongo driver. It will, as the name would indicate, find a single document in the collection. This can be very useful to get an idea about the schema of the collection. Since we currently only have one record in our database, it should be perfect for getting our information out. I’m also going to include a new import pprint so that when we print out our data in a pretty fashion.

import pprint

print("\nPretty Printed document: \n")
pprint.pprint(recipes.find_one())

Our output should be as follows, with the _id value being different on your system:

Pretty Print: 

{'_id': ObjectId('588541a0146bde28a08217d4'),
 'author': 'Biff Tannen',
 'cook time': 0,
 'description': 'Yummy drink',
 'ingredients': [{'name': 'milk', 'quantity': 8, 'unit of measure': 'ounce'},
                 {'name': 'chocolate syrup',
                  'quantity': 2,
                  'unit of measure': 'ounce'}],
 'prep time': 0,
 'title': 'chocolate milk',
 'uploaded_by': 'kenwalger',
 'yield': {'quantity': 1, 'unit': 'glass'}}

Cool! We have seen how to do two of the four CRUD tasks already! And through the pymongo driver, it has been relatively straightforward to do so. We have a basic recipe for chocolate milk in our cookbook.

Update and Delete

But wait, we incorrectly attributed the recipe to Biff Tannen. As any fan of the Back to the Future movie series knows, chocolate milk is George McFly’s drink, not his arch enemy Biff’s. Let’s Update our database with that change using the update_one() method with the $set operator and print out the new record to make sure it accomplished what we wanted.

recipes.update_one({'title': 'chocolate milk'},
                   {'$set': {'author': 'George McFly'}
                    }
                   )
print("\nShould be George McFly: ")
pprint.pprint(recipes.find_one({'author': 'George McFly'}))

Great, and our output should now reflect that change and we can tell it is the same record because the collection unique _id value is still the same.

Should be George McFly: 
{'_id': ObjectId('588541a0146bde28a08217d4'),
 'author': 'George McFly',
 'cook time': 0,
 'description': 'Yummy drink',
 'ingredients': [{'name': 'milk', 'quantity': 8, 'unit of measure': 'ounce'},
                 {'name': 'chocolate syrup',
                  'quantity': 2,
                  'unit of measure': 'ounce'}],
 'prep time': 0,
 'title': 'chocolate milk',
 'uploaded_by': 'kenwalger',
 'yield': {'quantity': 1, 'unit': 'glass'}}

We have one task left, Delete. Which uses the delete_one() method. We need to pass in an argument for which document to delete which, given that our current collection only has one recipe in there we could use a variety of things to select it. Traditionally we would want to utilize the unique identifier _id to ensure that we are only deleting the document that we want.

recipes.delete_one('_id': ObjectId('588541a0146bde28a08217d4'))

Is all we need to do and our collection is empty again.

CRUD Wrap Up

In summary, we learned how to Create, Read, Update, and Delete documents in MongoDB in Python using the PyMongo driver. There is much improvement on the code to be made though by refactoring things into functions, error handling, etc. As mentioned at the top of this post, the next post will be on Bottle specifically to get up and running with that framework. After that, we will integrate MongoDB with Bottle at which time we will see some best practices for error handling and making these CRUD steps into functions.

Please let me know what you think in the comments below or reach out on Twitter where I am @kenwalger.

The code for this post can be seen in the GitHub repository here.

Happy coding!

Facebooktwitterredditlinkedinmail