Level 3: App Engine Python Programming - App Engine Overview

This is the content from a talk at Google's I/O Extended 2015 in Pittsburgh.  Basic knowledge of Python, HTML, CSS, and JavaScript is assumed.

What is App Engine?
  • Google App Engine lets you build and run applications on Google infrastructure.
  • It's easy to create, easy to maintain, and easy to scale, and it's free to get started.
  • What this means for you, as a developer: you can concentrate on coding without worrying about infrastructure, servers, databases, bandwidth, or any of those non-coding hassles.

Some Included Services and Features Include:
  • Multiple languages: Python, Java, PHP, and Go
  • Data storage
  • File uploads
  • Image manipulation
  • Email
  • Logging
  • Scheduling and cron
  • Simple user management

Get Started Quickly (in Python)
  1. Set up the software.
    1. Download the SDK: https://cloud.google.com/appengine/downloads/
    2. Install the SDK
    3. Download Python: https://www.python.org/downloads/
    4. Install Python
  2. Create your application.
    1. Go to http://appspot.com
    2. Log in with your Google account
    3. Create an application
    4. Give it an Application Identifier (remember this) and a title
  3. Write some code.
    1. Create a directory
    2. Create an app.yaml file and specify the Application Identifier you chose above
    3. Build models and request handlers in Python
    4. Write front end code
  4. Test and deploy your application.
    1. Open Google App Engine Launcher
    2. Choose File => Add Existing Application and select the directory you created
    3. Click the "Run" button and test it out in your browser at http://localhost:8080/
    4. When you're ready, click the "Deploy" button and navigate to your app at http://your-application-identifier.appspot.com/

Code Walkthrough
The team I work with has gotten a reputation for practical jokes around the office, and we've started to get some requests from other teams for advice or help with pranks.  To demonstrate some of what App Engine can do, we built this simple service that allows users to submit a request for help with pranks.

The app.yaml File
The first thing to start with is our app.yaml file:

The app.yaml file describes how our application should behave and where App Engine should look for our resources.  
  • On line 1, you can see that our application's ID is requestprank, and when we deploy it we can find it at http://requestprank.appspot.com/.  
  • On line 2, we specify our application's version as v1; App Engine retains a copy of every version we specify when we deploy our application, and which version is being served can be changed in the Developers Console.
  • On line 3, we state that we are using the Python 2.7 runtime.
  • On line 4, we use api_version identify the version of the Python runtime we want to use when App Engine serves our application.  Right now there's only one - api_version: 1.
  • On line 5, we state that our application is threadsafe because we're using WSGI and we can handle concurrent requests properly.
  • On line 7, we begin our handlers area.  This is where we'll tell App Engine how different URLs should be handled.
  • On line 8, we'll use a regular expression to show that any url we service should be handled by the script specified on line 9 - main.app.  We're going to create an application in our main.py Python file and call it app.

Now that we have a decent app.yaml, we can walk through what we're doing in main.py.

If you open main.py, you'll see that it starts with import statements and some HTML.  We're going to skip around a bit to talk about what it's actually doing.  The very short summary is that we're using App Engine's default web framework - called webapp2.  webapp2 allows us to define RequestHandler classes that service URLs for different HTTP requests. We write code in each of those RequestHandler classes to receive a request, evaluate the request parameters, and produce some sort of response.

You'll notice on line 185 in main.py, we're creating a list of mappings.  We'll use this list of tuples to identify which RequestHandler classes should handle requests from different URLs.

If you inspect the code, you'll see we're adding to this list on lines 215, 243, 261, 281, and 308.  Each one of these identifies a new URL and RequestHandler that will handle requests made to our app.

At the very end of the file, we'll initialize our application and pass in these mappings.

This creates a new WSGIApplication with our mappings (and we're turning on debug).

RequestHandler Classes
The real power in our App Engine application is in each of our RequestHandler classes.  Let's take a look at some of those, starting with the MainHandler.

  • Line 199 only contains a long comment, for separating the code and making it a bit more readable.
  • Line 200 defines the MainPageHandler class. You can see here that our class extends the webapp2.RequestHandler class.
  • Line 201 defines a get method for our class - this will allow us to service HTTP GET requests.
  • On lines 202 and 204, we are using the App Engine Users service to determine if a user is logged in to our application.  This users service is imported on line 6 in main.py - it allows us to easily identify Google users and allow them to log in to our application.  We check to see if anything was returned by the service on line 204 - if a user hasn't logged in, then nothing will be returned.
  • On line 203, we use the RequestHandler's response variable to write out some HTML.  Ordinarily we wouldn't write HTML inline like this, but all of the code for this example is intended to fit within a single file.
  • On line 205, we check to see if the current user is an admin user, and if so, on line 206 we write out a JavaScript function to show all of the prank requests our application has received.  We'll get to that a bit later.
  • On line 208, if the user is not an admin user, we will simply call JavaScript to display the main form for submitting a prank.
  • On line 210, if we don't have a user, we're just going to use the users service to construct a URL that can be visited to log in to the site.
  • On line 213, we output the HTML footer.  Again, we typically wouldn't write inline HTML, but this is done for simplicity here.
  • On line 215, we add to the mappings list; we're going to use MainPageHandler to service root requests (i.e., requests directly to requestprank.appspot.com).
What happens with all of this:
  1. All of the HTML, CSS, and JavaScript on lines 10-180 in our main.py file is written out as a response.
  2. If an admin user is logged in, we'll call a JavaScript function to show the site's prank requests.
  3. If a regular user is logged in, we'll call a JavaScript function to show the site's main form.
  4. If no user is logged in, we'll simply display a link to log in to the site.

Of course, we're going to need to do more than just display a front page.  We'll also want to be able to handle user requests.  If you look at the code for the showMainForm() function in JavaScript, you'll see that when the initial form is submitted, it calls the submitRequest() function, which sends the HTTP request to the URL requestprank.appspot.com/save.  If you look on 243, you can see that the "/save" URL is handled by the SaveRequestHandler.  This RequestHandler is defined on lines 217-243.

  • Our RequestHandler class is defined on line 218.
  • On line 219 we define a post method - this will allow our RequestHandler to respond to HTTP POST requests.
  • On line 220 we retrieve the current user, and check to see if a user was returned on line 221.
  • On lines 222 and 223, we retrieve the "description" and "timeline" parameters from the request, and populate the request_detail and timeline variables with them.  Note that we are calling the "clean" function defined on lines 188-189 here.
    • The clean function removes double quote characters because they will break the JSON we're going to use later.
    • It also removes < characters so that our page's HTML won't be broken if a user tries to insert HTML code.
    • Finally, it replaces endlines with <br> tags so that they will be formatted as expected.
  • On lines 224-225, we check the description value and output a message if it's too long.
  • On lines 226-227, we check the email value and output a message if it's too long.
  • The next section (lines 229-239) is wrapped in a try/except block so that we can return a message to the user if there is an issue storing data.
  • If there are no issues, on lines 230-236 we create a PrankRequest object, populate its values, and store it.

NDB Model Classes
This introduces data storage in App Engine; the PrankRequest class is defined in lines 192-197.  This is class that extends Model from the ndb module.  NDB is a Python API in App Engine that allows a user to store and retrieve data in a replicated, distributed, object-based fashion.

  • On line 192 we define the class; note that it is a subclass of ndb.Model.
  • We define properties on lines 193-197.  
    • requestor is a String property.  This will the requestor's email address.
    • time_requested is a DateTime property.  This is just a timestamp for when the request was made.
    • description is a Text property.  This works similar to a String property but the String property is limited to only 500 characters.
    • timeline is a String property.  It just represents the user's input of when they'd like to execute their prank.
    • hidden is a Boolean property - we'll use this to hide our requests without losing the data.
There's not much more to simple data storage in App Engine than that - you define an ndb.Model class, populate its values, then just call the Model's put() method.

We will also want to list the requests for an admin user - as displayed in the showRequests() function in our JavaScript (showRequests() makes an Ajax call, which calls respondToShowRequests(), which calls showRequestList(); showRequestList() actually builds the HTML).  The Ajax request we make in the showRequests() function is sent to the "/list" URL; our ListRequestHandler is the RequestHandler that is mapped to that URL.  That RequestHandler is defined on lines 284-306.

  • We define the ListRequestHandler and the post method on lines 284 and 285.
  • We retrieve the current user and check to see if the user is an admin on lines 286 and 287.
  • We're going to build a JSON response here.  We start writing a JSON array on line 288.
  • On line 289 we construct a query; we're going to filter any PrankRequest objects marked as hidden.  This query will allow us to retrieve stored PrankRequest objects from NDB.
  • Lines 290 and 292-295 are just used to insert commas in the right place in our response.
  • Lines 297-304 convert our object to a JSON string in the response.  Note that on line 298 we use the Key for this Model.  The Key is a built-in identifier for the Model object.  We use the urlsafe version of this so that we can pass it back through JSON.
  • Lines 305-306 are used if there is no admin user logged in.
This should create a JSON response with an array containing all of the prank requests that have not been hidden.  Note that we don't have to apply special formatting to the values here because they were properly formatted prior to storage.

We can also hide the requests; this is done by sending an Ajax request to the "/hide" URL which is mapped to the HideRequestHandler on lines 264-279.

  • We define the class, the post method, and check the user on lines 264-267.
  • We retrieve the ID on line 268.  We'll use this on line 270 to build the Key for this PrankRequest object.
  • Once we have a Key constructed, we can easily retrieve the PrankRequest object by calling get() on line 271.
  • After we've retrieved the object, we just set the hidden property to True on line 272, and save it again on line 273.
  • After everything has been stored, we write OK to the response to identify that everything executed properly.
  • If there are any issues, we write the messages to the user or ask the user to log in on lines 276-279.

The only RequestHandler left is the one to accept a message and send an email to the requestor.  That is done in the SendMessageHandler on lines 246-259.

  • We define the class, the post method, and check the user on lines 246-249.  
  • On lines 250, 252, and 253, we retrieve the PrankRequest based on the ID parameter.
  • We create parameters for an email on lines 254-257.
  • On line 258, we send a message.  It's as simple as identifying an address to send the message to, the admin user to send the message from, the subject, and text of the message.  Once you have those, you can simply call the send_mail function in the mail module.

That's it - it's a simple application at just over 300 lines of code, but allows you build a user interface, communicate via Ajax and JSON, process and store data in NDB, and send email messages.  Using App Engine you can go from an idea like this to a live service in just a few hours.

Thanks for reading!

Timothy James,
Jun 2, 2015, 10:44 AM
Timothy James,
Jun 2, 2015, 10:44 AM