Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / Python

Google App Engine - Python Tutorial

0.00/5 (No votes)
26 Nov 2014CC (Attr 3U)24 min read 23.5K  
By the end of the tutorial, you will have implemented a working application, a simple guest book that lets users post messages to a public message board.

This article is for our sponsors at CodeProject. These articles are intended to provide you with information on products and services that we consider useful and of value to developers

Introduction

Welcome to Google App Engine! Creating an App Engine application is easy, and only takes a few minutes. And it's free to start: upload your app and share it with users right away, at no charge and with no commitment required.

Google App Engine applications can be written in the Python 2.7, Java, Go or PHP programming languages. This tutorial covers Python 2.7. If you would prefer to use Java, Go or PHP to build your applications, see the Java, Go or PHP guides.

In this tutorial, you will learn how to:

  • build an App Engine application using Python
  • use the webapp2 web application framework
  • use the App Engine datastore with the Python modeling API
  • integrate an App Engine application with Google Accounts for user authentication
  • use Jinja2 templates with your app
  • upload your app to App Engine

By the end of the tutorial, you will have implemented a working application, a simple guest book that lets users post messages to a public message board.

Run/Modify

While going through this tutorial you will see buttons that allow you to test the code sample right in your browser in the Google Cloud Playground. These are labeled "Run/Modify." You can even make changes to the code and see the results in real-time!

Get set up

Before we continue, you will need to download the Google App Engine Python SDK, which includes a web server application that simulates the App Engine environment, and tools to deploy your application to the App Engine production environment. Follow the directions for your operating system, then come back here so we can get going!

Hello, World!

Let's begin by implementing a tiny application that displays a short message.

Creating a Simple Request Handler

Create a directory named helloworld. All files for this application reside in this directory.

Inside the helloworld directory, create a file named helloworld.py, and give it the following contents:

import webapp2

class MainPage(webapp2.RequestHandler):
    def get(self):
        self.response.headers['Content-Type'] = 'text/plain'
        self.response.write('Hello, World!')

application = webapp2.WSGIApplication([
    ('/', MainPage),
], debug=True)

This Python script responds to a request with an HTTP header that describes the content and the message Hello, World!.

Note: Ensure that you save the files you create as plain text. You may encounter errors otherwise.

Creating the Configuration File

An App Engine application has a configuration file called app.yaml. Among other things, this file describes which handler scripts should be used for which URLs.

Inside the helloworld directory, create a file named app.yaml with the following contents:

import webapp2

class MainPage(webapp2.RequestHandler):
    def get(self):
        self.response.headers['Content-Type'] = 'text/plain'
        self.response.write('Hello, World!')

application = webapp2.WSGIApplication([
    ('/', MainPage),
], debug=True)

From top to bottom, this configuration file says the following about this application:

  • The application identifier is your-app-id. Every new application on App Engine has a unique application identifier. You'll choose the identifier for your application when you register it in the next step. Until then you can just leave the value here set to your-app-id because this value is not important when developing locally.
  • This is version number 1 of this application's code. If you adjust this before uploading new versions of your application software, App Engine will retain previous versions, and let you roll back to a previous version using the administrative console.
  • This code runs in the python27 runtime environment, API version 1. Additional runtime environments and languages may be supported in the future.
  • This application is threadsafe so the same instance can handle several simultaneous requests. Threadsafe is an advanced feature and may result in erratic behavior if your application is not specifically designed to be threadsafe.
  • Every request to a URL whose path matches the regular expression /.* (all URLs) should be handled by the application object in the helloworld module.

The syntax of this file is YAML. For a complete list of configuration options, see the app.yaml reference.

Testing the Application

With a handler script and configuration file mapping every URL to the handler, the application is complete. You can now test it with the web server included with the App Engine SDK.

If you're using the Google App Engine Launcher, you can set up the application by selecting the File menu, Add Existing Application..., then selecting the helloworld directory. Select the application in the app list, click the Run button to start the application, then click the Browse button to view it. Clicking Browse simply loads (or reloads) http://localhost:8080/ in your default web browser.

If you're not using Google App Engine Launcher, start the web server with the following command, giving it the path to the helloworld directory:

<path-to-Python-SDK>/dev_appserver.py helloworld/

The web server is now running, listening for requests on port 8080. You can test the application by visiting the following URL in your web browser:

For more information about running the development web server, including how to change which port it uses, see the Dev Web Server reference, or run the command with the option --help.

Iterative Development

You can leave the web server running while you develop your application. The web server knows to watch for changes in your source files and reload them if necessary.

Try it now: Leave the web server running, then edit helloworld.py to change Hello, World! to something else. Reload http://localhost:8080/ or click Browse in Google App Engine Launcher to see the change.

To shut down the web server, make sure the terminal window is active, then press Control-C (or the appropriate "break" key for your console), or click Stop in Google App Engine Launcher.

You can leave the web server running for the rest of this tutorial. If you need to stop it, you can restart it again by running the command above.

The Web Server Gateway Interface (WSGI) standard is simple, but it would be cumbersome to write all of the code that uses it by hand. Web application frameworks handle these details for you, so you can focus your development efforts on your application's features. Google App Engine supports any framework written in pure Python that speaks WSGI, including Django, CherryPy, Pylons, web.py, and web2py. You can bundle a framework of your choosing with your application code by copying its code into your application directory.

App Engine includes a simple web application framework, called webapp2. The webapp2 framework is already installed in the App Engine environment and in the SDK, so you do not need to bundle it with your application code to use it. We will use webapp2 for the rest of this tutorial.

Explaining the webapp2 Framework

The Web Server Gateway Interface (WSGI) standard is simple, but it would be cumbersome to write all of the code that uses it by hand. Web application frameworks handle these details for you, so you can focus your development efforts on your application's features. Google App Engine supports any framework written in pure Python that speaks WSGI, including Django, CherryPy, Pylons, web.py, and web2py. You can bundle a framework of your choosing with your application code by copying its code into your application directory.

App Engine includes a simple web application framework, called webapp2. The webapp2 framework is already installed in the App Engine environment and in the SDK, so you do not need to bundle it with your application code to use it. We will use webapp2 for the rest of this tutorial.

Hello, webapp2!

A webapp2 application has two parts:

  • one or more RequestHandler classes that process requests and build responses
  • a WSGIApplication instance that routes incoming requests to handlers based on the URL

Let's take another look at our friendly greeting application:

import webapp2

class MainPage(webapp2.RequestHandler):
    def get(self):
        self.response.headers['Content-Type'] = 'text/plain'
        self.response.write('Hello, World!')

application = webapp2.WSGIApplication([
    ('/', MainPage),
], debug=True)

What webapp2 Does

This code defines one request handler, MainPage, mapped to the root URL (/). When webapp2 receives an HTTP GET request to the URL /, it instantiates the MainPage class and calls the instance's get method. Inside the method, information about the request is available using self.request. Typically, the method sets properties on self.response to prepare the response, then exits. webapp2 sends a response based on the final state of the MainPage instance.

The application itself is represented by a webapp2.WSGIApplication instance. The parameter debug=true passed to its constructor tells webapp2 to print stack traces to the browser output if a handler encounters an error or raises an uncaught exception. You may wish to remove this option from the final version of your application.

We'll use a few more features of webapp2 later in this tutorial. For more information about webapp2, see the webapp2 documentation.

Using the Users Service

Google App Engine provides several useful services based on Google infrastructure, accessible by applications using libraries included with the SDK. One such service is the Users service, which lets your application integrate with Google user accounts. With the Users service, your users can use the Google accounts they already have to sign in to your application.

Let's use the Users service to personalize this application's greeting.

Using Users

Edit helloworld/helloworld.py again, and replace its contents with the following:

from google.appengine.api import users

import webapp2


class MainPage(webapp2.RequestHandler):

    def get(self):
        # Checks for active Google account session
        user = users.get_current_user()

        if user:
            self.response.headers['Content-Type'] = 'text/plain'
            self.response.write('Hello, ' + user.nickname())
        else:
            self.redirect(users.create_login_url(self.request.uri))


application = webapp2.WSGIApplication([
    ('/', MainPage),
], debug=True)

Reload the page in your browser. Your application redirects you to the local version of the Google sign-in page suitable for testing your application. You can enter any username you'd like in this screen, and your application will see a fake User object based on that username.

When your application is running on App Engine, users will be directed to the Google Accounts sign-in page, then redirected back to your application after successfully signing in or creating an account.

The Users API

Let's take a closer look at the new pieces:

# Checks for active Google account session
user = users.get_current_user()

If the user is already signed in to your application, get_current_user() returns the User object for the user. Otherwise, it returns None.

if user:
    self.response.headers['Content-Type'] = 'text/plain'
    self.response.write('Hello, ' + user.nickname())

If the user has signed in, display a personalized message, using the nickname associated with the user's account.

else:
    self.redirect(users.create_login_url(self.request.uri))

If the user has not signed in, tell webapp2 to redirect the user's browser to the Google account sign-in screen. The redirect includes the URL to this page (self.request.uri) so the Google account sign-in mechanism will send the user back here after the user has signed in or registered for a new account.

For more information about the Users API, see the Users reference.

Handling Forms with webapp2

If we want users to be able to post their own greetings, we need a way to process information submitted by the user with a web form. The webapp2 framework makes processing form data easy.

From Hello World to Guestbook

In order to prepare the Hello World app we've created thus far, please make the following changes:

  • Rename the top level helloworld directory to guestbook
  • Rename helloworld.py to guestbook.py
  • Replace the handlers section of app.yaml with:
handlers:
- url: /.*
  script: guestbook.application

Restart the development server using the new guestbook directory.

Handling Web Forms With webapp2

Declare that you are using webapp2 by adding this libraries section to your app.yaml:

libraries:
- name: webapp2
  version: latest

Replace the contents of guestbook/guestbook.py with the following:

import cgi
from google.appengine.api import users
import webapp2

MAIN_PAGE_HTML = """\
<html>
  <body>
    <form action="/sign" method="post">
      <div><textarea name="content" rows="3" cols="60"></textarea></div>
      <div><input type="submit" value="Sign Guestbook"></div>
    </form>
  </body>
</html>
"""

class MainPage(webapp2.RequestHandler):
    def get(self):
        self.response.write(MAIN_PAGE_HTML)

class Guestbook(webapp2.RequestHandler):
    def post(self):
        self.response.write('<html><body>You wrote:<pre>')
        self.response.write(cgi.escape(self.request.get('content')))
        self.response.write('</pre></body></html>')

application = webapp2.WSGIApplication([
    ('/', MainPage),
    ('/sign', Guestbook),
], debug=True)

Reload the page to see the form, then try submitting a message.

This version has two handlers: MainPage, mapped to the URL /, displays a web form. Guestbook, mapped to the URL /sign, displays the data submitted by the web form.

The Guestbook handler has a post() method instead of a get() method. This is because the form displayed by MainPage uses the HTTP POST method (method="post") to submit the form data. If for some reason you need a single handler to handle both GET and POST actions to the same URL, you can define a method for each action in the same class.

The code for the post() method gets the form data from self.request. Before displaying it back to the user, it uses cgi.escape() to escape HTML special characters to their character entity equivalents. cgi is a module in the standard Python library; see the documentation for cgi for more information.

Using the Datastore

Storing data in a scalable web application can be tricky. A user could be interacting with any of dozens of web servers at a given time, and the user's next request could go to a different web server than the previous request. All web servers need to be interacting with data that is also spread out across dozens of machines, possibly in different locations around the world.

With Google App Engine, you don't have to worry about any of that. App Engine's infrastructure takes care of all of the distribution, replication, and load balancing of data behind a simple API—and you get a powerful query engine and transactions as well.

App Engine's data repository, the High Replication Datastore (HRD), uses the Paxos algorithm to replicate data across multiple datacenters. Data is written to the Datastore in objects known as entities. Each entity has a key that uniquely identifies it. An entity can optionally designate another entity as its parent; the first entity is a child of the parent entity. The entities in the Datastore thus form a hierarchically-structured space similar to the directory structure of a file system. An entity's parent, parent's parent, and so on recursively, are its ancestors; its children, children's children, and so on, are its descendants. An entity without a parent is a root entity.

The Datastore is extremely resilient in the face of catastrophic failure, but its consistency guarantees may differ from what you're familiar with. Entities descended from a common ancestor are said to belong to the same entity group; the common ancestor's key is the group's parent key, which serves to identify the entire group. Queries over a single entity group, called ancestor queries, refer to the parent key instead of a specific entity's key. Entity groups are a unit of both consistency and transactionality: whereas queries over multiple entity groups may return stale, eventually consistent results, those limited to a single entity group always return up-to-date, strongly consistent results.

The sample application in this guide organizes related entities into entity groups, and uses ancestor queries on those entity groups to return strongly consistent results. In the example code comments, we highlight some ways this approach might affect the design of your application. For more detailed information, see Structuring Data for Strong Consistency.

A Complete Example Using the Datastore

Here is a new version of guestbook/guestbook.py that creates a page footer that stores greetings in the Datastore. The rest of this page discusses excerpts from this larger example, organized under the topics of storing the greetings and retrieving them.

import cgi
import urllib

from google.appengine.api import users
from google.appengine.ext import ndb

import webapp2

MAIN_PAGE_FOOTER_TEMPLATE = """\
    <form action="/sign?%s" method="post">
      <div><textarea name="content" rows="3" cols="60"></textarea></div>
      <div><input type="submit" value="Sign Guestbook"></div>
    </form>
    <hr>
    <form>Guestbook name:
      <input value="%s" name="guestbook_name">
      <input type="submit" value="switch">
    </form>
    <a href="%s">%s</a>
  </body>
</html>
"""

DEFAULT_GUESTBOOK_NAME = 'default_guestbook'

# We set a parent key on the 'Greetings' to ensure that they are all in the same
# entity group. Queries across the single entity group will be consistent.
# However, the write rate should be limited to ~1/second.

def guestbook_key(guestbook_name=DEFAULT_GUESTBOOK_NAME):
    """Constructs a Datastore key for a Guestbook entity with guestbook_name."""
    return ndb.Key('Guestbook', guestbook_name)

class Greeting(ndb.Model):
    """Models an individual Guestbook entry."""
    author = ndb.UserProperty()
    content = ndb.StringProperty(indexed=False)
    date = ndb.DateTimeProperty(auto_now_add=True)

class MainPage(webapp2.RequestHandler):
    def get(self):
        self.response.write('<html><body>')
        guestbook_name = self.request.get('guestbook_name',
                                          DEFAULT_GUESTBOOK_NAME)

        # Ancestor Queries, as shown here, are strongly consistent with the High
        # Replication Datastore. Queries that span entity groups are eventually
        # consistent. If we omitted the ancestor from this query there would be
        # a slight chance that Greeting that had just been written would not
        # show up in a query.
        greetings_query = Greeting.query(
            ancestor=guestbook_key(guestbook_name)).order(-Greeting.date)
        greetings = greetings_query.fetch(10)

        for greeting in greetings:
            if greeting.author:
                self.response.write(
                        '<b>%s</b> wrote:' % greeting.author.nickname())
            else:
                self.response.write('An anonymous person wrote:')
            self.response.write('<blockquote>%s</blockquote>' %
                                cgi.escape(greeting.content))

        if users.get_current_user():
            url = users.create_logout_url(self.request.uri)
            url_linktext = 'Logout'
        else:
            url = users.create_login_url(self.request.uri)
            url_linktext = 'Login'

        # Write the submission form and the footer of the page
        sign_query_params = urllib.urlencode({'guestbook_name': guestbook_name})
        self.response.write(MAIN_PAGE_FOOTER_TEMPLATE %
                            (sign_query_params, cgi.escape(guestbook_name),
                             url, url_linktext))

class Guestbook(webapp2.RequestHandler):
    def post(self):
        # We set the same parent key on the 'Greeting' to ensure each Greeting
        # is in the same entity group. Queries across the single entity group
        # will be consistent. However, the write rate to a single entity group
        # should be limited to ~1/second.
        guestbook_name = self.request.get('guestbook_name',
                                          DEFAULT_GUESTBOOK_NAME)
        greeting = Greeting(parent=guestbook_key(guestbook_name))

        if users.get_current_user():
            greeting.author = users.get_current_user()

        greeting.content = self.request.get('content')
        greeting.put()

        query_params = {'guestbook_name': guestbook_name}
        self.redirect('/?' + urllib.urlencode(query_params))

application = webapp2.WSGIApplication([
    ('/', MainPage),
    ('/sign', Guestbook),
], debug=True)

Replace guestbook/guestbook.py with this, then reload http://localhost:8080/ in your browser. Post a few messages to verify that messages get stored and displayed correctly.

Warning! Exercising the queries in your application locally causes App Engine to create or update index.yaml. If index.yaml is missing or incomplete, you will see index errors when your uploaded application executes queries for which the necessary indexes have not been specified. To avoid missing index errors in production, always test new queries at least once locally before uploading your application. See Python Datastore Index Configuration for more information.

Storing the Submitted Greetings

App Engine includes a data modeling API for Python. It's similar to Django's data modeling API, but uses App Engine's scalable Datastore behind the scenes.

To use the data modeling API, our example imports the google.appengine.ext.ndb module:

# Imports the NDB data modeling API
from google.appengine.ext import ndb

For the guestbook application, we want to store greetings posted by users. Each greeting includes the author's name, the message content, and the date and time the message was posted so we can display messages in chronological order. The following code defines our data model:

class Greeting(ndb.Model):
    """Models an individual Guestbook entry."""
    author = ndb.UserProperty()
    content = ndb.StringProperty(indexed=False)
    date = ndb.DateTimeProperty(auto_now_add=True)

This defines a Greeting model with three properties: author whose value is a google.appengine.api.user object, content whose value is a string, and date whose value is a datetime.datetime.

Some property constructors take parameters to further configure their behavior. Giving the ndb.StringProperty constructor the indexed=False parameter says that values for this property will not be indexed. This saves us writes which aren't needed since we never use that property in a query. Giving the ndb.DateTimeProperty constructor an auto_now_add=True parameter configures the model to automatically give new objects a datetime stamp of the time the object is created, if the application doesn't otherwise provide a value. For a complete list of property types and their options, see NDB Properties.

Now that we have a data model for greetings, the application can use the model to create new Greeting objects and put them into the Datastore. The Guestbook handler creates new greetings and saves them to the Datastore:

class Guestbook(webapp2.RequestHandler):
    def post(self):
        # We set the same parent key on the 'Greeting' to ensure each Greeting
        # is in the same entity group. Queries across the single entity group
        # will be consistent. However, the write rate to a single entity group
        # should be limited to ~1/second.
        guestbook_name = self.request.get('guestbook_name',
                                          DEFAULT_GUESTBOOK_NAME)
        greeting = Greeting(parent=guestbook_key(guestbook_name))

        if users.get_current_user():
            greeting.author = users.get_current_user()

        greeting.content = self.request.get('content')
        greeting.put()

        query_params = {'guestbook_name': guestbook_name}
        self.redirect('/?' + urllib.urlencode(query_params))

This Guestbook handler creates a new Greeting object, then sets its author and content properties with the data posted by the user. The parent of Greeting is a Guestbook entity. There's no need to create the Guestbook entity before setting it to be the parent of another entity. In this example, the parent is used as a placeholder for transaction and consistency purposes. See the Transactions page for more information. Objects that share a common ancestor belong to the same entity group. It does not set the date property, so date is automatically set to the present, using auto_now_add=True, which we configured above.

Finally, greeting.put() saves our new object to the Datastore. If we had acquired this object from a query, put() would have updated the existing object. Since we created this object with the model constructor, put() adds the new object to the Datastore.

Because querying in the High Replication Datastore is strongly consistent only within entity groups, we assign all of one book's greetings to the same entity group in this example by setting the same parent for each greeting. This means a user will always see a greeting immediately after it was written. However, the rate at which you can write to the same entity group is limited to 1 write to the entity group per second. When you design a real application you'll need to keep this fact in mind. Note that by using services such as Memcache, you can mitigate the chance that a user won't see fresh results when querying across entity groups immediately after a write.

Retrieving Submitted Greetings

The App Engine Datastore has a sophisticated query engine for data models. Because the App Engine Datastore is not a traditional relational database, queries are not specified using SQL. Instead, data is queried one of two ways: Either via Datastore queries, or using an SQL-like query language called GQL. To access the full range of Datastore query capabilities, we recommend using Datastore queries over GQL.

The MainPage handler retrieves and displays previously submitted greetings. The Datastore query happens here:

greetings_query = Greeting.query(
    ancestor=guestbook_key(guestbook_name)).order(-Greeting.date)
greetings = greetings_query.fetch(10)

A Word About Datastore Indexes

Every query in the App Engine Datastore is computed from one or more indexes—tables that map ordered property values to entity keys. This is how App Engine is able to serve results quickly regardless of the size of your application's Datastore. Many queries can be computed from the builtin indexes, but for queries that are more complex the Datastore requires a custom index. Without a custom index, the Datastore can't execute these queries efficiently.

For example, our guest book application above filters by guestbook and orders by date, using an ancestor query and a sort order. This requires a custom index to be specified in your application's index.yaml file. You can edit this file manually or, as noted in the warning box earlier on this page, you can take care of it automatically by running the queries in your application locally. Once the index is defined in index.yaml, uploading your application will also upload your custom index information.

The definition for the query in your index.yaml file looks like this:

indexes:
- kind: Greeting
  ancestor: yes
  properties:
  - name: date
    direction: desc

You can read all about Datastore indexes in the Datastore Indexes page. You can read about the proper specification for your index.yaml file in Python Datastore Index Configuration.

We now have a working guest book application that authenticates users using Google accounts, lets them submit messages, and displays messages other users have left. Because App Engine handles scaling automatically, we will not need to revisit this code as our application gets popular.

This latest version mixes HTML content with the code for the MainPage handler. This will make it difficult to change the appearance of the application, especially as our application gets bigger and more complex. Let's use templates to manage the appearance, and introduce static files for a CSS stylesheet.

Using Templates

HTML embedded in code is messy and difficult to maintain. It's better to use a templating system, where the HTML is kept in a separate file with special syntax to indicate where the data from the application appears. There are many templating systems for Python: EZT, Cheetah, ClearSilver, Quixote, Django, and Jinja2 are just a few. You can use your template engine of choice by bundling it with your application code.

For your convenience, App Engine includes the Django and Jinja2 templating engines.

Using Jinja2 Templates

First modify the libraries section at the bottom of guestbook/app.yaml:

libraries:
- name: webapp2
  version: latest
- name: jinja2
  version: latest

This configuration makes the newest supported version of Jinja2 available to your application. To avoid possible compatibility issues, serious applications should use an actual version number rather than latest.

Now modify the statements at the top of guestbook/guestbook.py:

import os
import urllib

from google.appengine.api import users
from google.appengine.ext import ndb

import jinja2
import webapp2


JINJA_ENVIRONMENT = jinja2.Environment(
    loader=jinja2.FileSystemLoader(os.path.dirname(__file__)),
    extensions=['jinja2.ext.autoescape'],
    autoescape=True)

Replace the MainPage handler with code that resembles the following:

class MainPage(webapp2.RequestHandler):

    def get(self):
        guestbook_name = self.request.get('guestbook_name',
                                          DEFAULT_GUESTBOOK_NAME)
        greetings_query = Greeting.query(
            ancestor=guestbook_key(guestbook_name)).order(-Greeting.date)
        greetings = greetings_query.fetch(10)

        if users.get_current_user():
            url = users.create_logout_url(self.request.uri)
            url_linktext = 'Logout'
        else:
            url = users.create_login_url(self.request.uri)
            url_linktext = 'Login'

        template_values = {
            'greetings': greetings,
            'guestbook_name': urllib.quote_plus(guestbook_name),
            'url': url,
            'url_linktext': url_linktext,
        }

        template = JINJA_ENVIRONMENT.get_template('index.html')
        self.response.write(template.render(template_values))

Finally, create a new file in the guestbook directory named index.html, with the following contents:

<!DOCTYPE html>
{% autoescape true %}
<html>
  <body>
    {% for greeting in greetings %}
      {% if greeting.author %}
        <b>{{ greeting.author.nickname() }}</b> wrote:
      {% else %}
       An anonymous person wrote:
      {% endif %}
      <blockquote>{{ greeting.content }}</blockquote>
    {% endfor %}

    <form action="/sign?guestbook_name={{ guestbook_name }}" method="post">
      <div><textarea name="content" rows="3" cols="60"></textarea></div>
      <div><input type="submit" value="Sign Guestbook"></div>
    </form>

    <hr>

    <form>Guestbook name:
      <input value="{{ guestbook_name }}" name="guestbook_name">
      <input type="submit" value="switch">
    </form>

    <a href="{{ url|safe }}">{{ url_linktext }}</a>

  </body>
</html>
{% endautoescape %}

Reload the page, and try it out.

JINJA_ENVIRONMENT.get_template(name) takes the name of a template file, and returns a template object. template.render(template_values) takes a dictionary of values, and returns the rendered text. The template uses Jinja2 templating syntax to access and iterate over the values, and can refer to properties of those values. In many cases, you can pass datastore model objects directly as values, and access their properties from templates.

Tip: An App Engine application has read-only access to all of the files uploaded with the project, the library modules, and no other files. The current working directory is the application root directory, so the path to index.html is simply "index.html".

Other templating languages

This example was done in Jinja2, but we also have App Engine starter projects that use Flask and Bottle in the Google Developers Console.

Every web application returns dynamically generated HTML from the application code, via templates or some other mechanism. Most web applications also need to serve static content, such as images, CSS stylesheets, or JavaScript files. For efficiency, App Engine treats static files differently from application source and data files. You can use App Engine's static files feature to serve a CSS stylesheet for this application.

Using Static Files

Unlike a traditional web hosting environment, Google App Engine does not serve files directly out of your application's source directory unless configured to do so. We named our template file index.html, but this does not automatically make the file available at the URL /index.html.

But there are many cases where you want to serve static files directly to the web browser. Images, CSS stylesheets, JavaScript code, movies and Flash animations are all typically stored with a web application and served directly to the browser. App Engine can serve specific files directly without you having to code your own handler.

Using Static Files

Edit guestbook/app.yaml and replace its contents with the following:

application: your-app-id
version: 1
runtime: python27
api_version: 1
threadsafe: true

handlers:
- url: /stylesheets
  static_dir: stylesheets

- url: /.*
  script: guestbook.application

libraries:
- name: webapp2
  version: latest
- name: jinja2
  version: latest

The new handlers section defines two handlers for URLs. When App Engine receives a request with a URL beginning with /stylesheets, it maps the remainder of the path to files in the stylesheets directory and, if an appropriate file is found, the contents of the file are returned to the client. All other URLs match the / path, and are handled by the application object in the guestbook module.

By default, App Engine serves static files using a MIME type based on the filename extension. For example, a file with a name ending in .css will be served with a MIME type of text/css. You can configure explicit MIME types by using the mime_type setting when configuring your handlers in app.yaml.

URL handler path patterns are tested in the order they appear in app.yaml, from top to bottom. In this case, the /stylesheets pattern will match before the /.* pattern will for the appropriate paths. For more information on URL mapping and other options you can specify in app.yaml, see the app.yaml reference.

Note: You can specify http_headers settings in the static directory handler to supply custom headers in the responses returned by the handler. This is useful, for example, for including the `Access-Control-Allow-Origin` header required to support CORS. For more information, see the documentation for http_headers under Static file handlers.

Create the directory guestbook/stylesheets/. In this new directory, create a new file named main.css with the following contents:

body {
  font-family: Verdana, Helvetica, sans-serif;
  background-color: #DDDDDD;
}

Finally, edit guestbook/index.html and insert the following lines between the <html> and <body> tags at the top:

<head>
  <link type="text/css" rel="stylesheet" href="https://cloud.google.com/stylesheets/main.css" />
</head>

Reload the page in your browser. The new version uses the stylesheet.

The time has come to reveal your finished application to the world.

Uploading Your Application

You create and manage App Engine applications using the Google Developers Console. Once you have registered an application ID for your application, you upload it to your website using appcfg.py, a command-line tool provided in the SDK. Or, if you're using Google App Engine Launcher, you can upload your application by clicking the Deploy button.

Note: Application IDs must begin with a letter. Once you register an application ID, you can delete it, but you can't re-register that same application ID after it has been deleted. You can skip these next steps if you don't want to register an ID at this time.

Note: If you have an App Engine Premier account, you can specify that your new application should reside in the European Union rather than the United States. Developers that do not have a Premier account need to fill out this form and enable billing for applications that should reside in the European Union.

Hosting applications in the European Union is especially useful if your users are closer to Europe than to the United States. There is less network latency and the End User Content will be stored at rest in the European Union. You must specify this location by clicking the "Edit" link in the "Location Options" section when you register the application; you cannot change it later.

Registering the Application

You create and manage App Engine applications from the Developers Console, at the following URL:

https://console.developers.google.com/

Google App Engine Launcher users can reach this URL by clicking the Dashboard button.

Sign in to App Engine using your Google account. If you do not have a Google account, you can create a Google account with an email address and password.

Note: You may have already created a project using the Google Developers Console. If this is the case, you do not have to create a new application. Your project has a title and an ID. In the instructions that follow, the project title and ID can be used wherever an application title and ID are mentioned. They are the same thing.

To create a new application, click the "Create an Application" button. Follow the instructions to register an application ID, a name unique to this application. If you elect to use the free appspot.com domain name, the full URL for the application will be http://your-app-id.appspot.com/. You can also purchase a top-level domain name for your app, or use one that you have already registered.

Note: The High Replication Datastore is required in order to use the Python 2.7 runtime. This is the default when creating new applications.

If you have an App Engine Premier account, you can specify that your new application should reside in the European Union rather than the United States. This is especially useful if your application's users are closer to Europe than to the United States. There is less network latency and the End User Content will be stored at rest in the European Union. You must specify this location when you register the application; you cannot change it later. Click the Edit link in the Location Options section; select a location option, either United States or European Union.

Edit the app.yaml file, then change the value of the application: setting from your-app-id to your registered application ID.

Uploading the Application

To upload your finished application to Google App Engine, run the following command:

appcfg.py update guestbook/

Or click Deploy in the Google App Engine Launcher and enter your Google username and password at the prompts.

If you work with the Git version control system, you can create a remote repository in Google's cloud, and configure your development environment to deploy the latest version of your code each time you push it to that repository. See Using Git to Push and Deploy.

http://your-app-id.appspot.com

Note: The Datastore Indexes may take some time to generate before your application is available. You will receive a NeedIndexError when accessing your app if the indexes are still in the process of being generated. This is a transient error for the example, so try a little later if at first you receive this exception.

Congratulations!

You have completed this tutorial. For more information on the subjects covered here, see the rest of the App Engine documentation.

Except as otherwise noted, the code samples of this page is licensed under the Apache 2.0 License.

License

This article, along with any associated source code and files, is licensed under The Creative Commons Attribution 3.0 Unported License