Académique Documents
Professionnel Documents
Culture Documents
tmpl=%2Fsystem%2Fapp%2Ftemplates%2Fprint%2F…
Objective/Description:
This codelab will demonstrate how to build applications using the Python version of Google App Engine. It is broken
down into three sections. After completing this codelab, you should be able to create (and deploy) applications on
Google App Engine using Python.
Part 1 Focuses on the basics which is to build a simple guestbook application. Incidentally, this application is based on
the existing "Getting Started" online tutorial in the official docs, but it also extends upon it a bit as well as offers more
detailed explanations.
Part 2 Focuses on providing a set of examples of how to use the various App Engine services such as email, XMPP.
Part 3 Shows how to use URLFetch, Cron, Task Queues and the Channel API to push browser updates from the
server. This is a standalone lab and can be run independently from the first two labs.
Requirements:
You have (at least) two options to do this codelab:
Introduction
https://sites.google.com/site/gdevelopercodelabs/app-engine/python-codelab?tmpl=%2Fsystem%2Fapp%2Ftemplates%2Fprint%2F&showPrintDialog=1
4/29/2018 https://sites.google.com/site/gdevelopercodelabs/app-engine/python-codelab?tmpl=%2Fsystem%2Fapp%2Ftemplates%2Fprint%2F…
Alternatives
If jumping directly into code like we do here is too overwhelming, and you wish to get more background and move more
slowly, go to the Google Developers Academy where you can take the intro to cloud computing & App Engine class first
then do that Python 101 class afterwards. Both the tutorial and Academy classes will get you about halfway through
part 1 of this codelab.
Notes
If you bring up the launcher and create a new application, it will create your files automatically and give you 1-click ability
to start the server. They will look like the following on Win32 and Mac OS X systems:
https://sites.google.com/site/gdevelopercodelabs/app-engine/python-codelab?tmpl=%2Fsystem%2Fapp%2Ftemplates%2Fprint%2F&showPrintDialog=1
4/29/2018 https://sites.google.com/site/gdevelopercodelabs/app-engine/python-codelab?tmpl=%2Fsystem%2Fapp%2Ftemplates%2Fprint%2F…
Regarding application names, you can choose whatever you want for development, but be aware that you will need to
rename it to deploy live. Pick something appropriate and unique. We suggest call it “helloworld” – keep it all lowercase;
users have had problems when using CamelCasing their app names.
https://sites.google.com/site/gdevelopercodelabs/app-engine/python-codelab?tmpl=%2Fsystem%2Fapp%2Ftemplates%2Fprint%2F&showPrintDialog=1
4/29/2018 https://sites.google.com/site/gdevelopercodelabs/app-engine/python-codelab?tmpl=%2Fsystem%2Fapp%2Ftemplates%2Fprint%2F…
Once you've cloned it, it shows up in your set of projects. Select that you wish to work on it by clicking on the blue arrow
button to the left (Figure 2b).
You'll then bring up the project and the app.yaml config file, as you can see here in Figure 2c:
https://sites.google.com/site/gdevelopercodelabs/app-engine/python-codelab?tmpl=%2Fsystem%2Fapp%2Ftemplates%2Fprint%2F&showPrintDialog=1
4/29/2018 https://sites.google.com/site/gdevelopercodelabs/app-engine/python-codelab?tmpl=%2Fsystem%2Fapp%2Ftemplates%2Fprint%2F…
You can also select the main application file main.py too (FIgure 2d):
By the way, we mentioned there are more than 2 options to proceeding with the codelab. If you're not on a PC or Mac and
doing Option 1 and not using Option 2's Cloud Playground, you may be using a Linux or some other system purely from
the command-line (no UIs at all). In this case, you'll need to enter the app code by hand. It’s not that bad, as you'll see in
the upcoming section.
#!/usr/bin/env python
import webapp2
class MainHandler(webapp2.RequestHandler):
def get(self):
self.response.write('Hello world!')
app = webapp2.WSGIApplication([
('/', MainHandler)
], debug=True)
Requests to / are handled by the MainHandler class, specifically its get() method
Configuration is done via the app.yaml file
main() creates a web application object and run_wsgi_app() executes the server loop
Now that you have an application handler file, you’re ready to go... that is, if using the Launcher or Cloud Playground,
you'll get this in the body of the app.yaml configuration file (or enter by hand if using neither):
1 application: helloworld
2 version: 1
3 runtime: python27
4 api_version: 1
5
6 handlers:
7 - url: .*
8 script: main.app
https://sites.google.com/site/gdevelopercodelabs/app-engine/python-codelab?tmpl=%2Fsystem%2Fapp%2Ftemplates%2Fprint%2F&showPrintDialog=1
4/29/2018 https://sites.google.com/site/gdevelopercodelabs/app-engine/python-codelab?tmpl=%2Fsystem%2Fapp%2Ftemplates%2Fprint%2F…
Option 1: Once you have both of these files, then you’re ready to run the server. It’s as easy as one click in the launcher. If
you are on the command-line, you need to run the server manually:
$ dev_appserver.py helloworld
Now you should be able to hit http://localhost:8080 (the port may be diffierent if using the Launcher, in which case it's
easier to just click the Run button in the UI) and see the expected output in your browser:
Option 2: Those using Cloud Playground do less work as there's already a server running. Just click the run button in
the web UI to see the exact same output (in the output window at the bottom), as shown here in Figure 3b:
Figure 3b. “Hello World!” in plain text from the Cloud Playground UI
Please get this working as you need to complete each section of the lab in order to move to the next.
Before moving onto the next part of the lab, make a few final tweaks: the first is to add a comma after the "('/',
MainHandler)" 2-tuple to avoid any issues when adding new handlers.
Also, you want to test out the dynamic nature of the development server: enclose "Hello World!" in <h1> tags so as to
output HTML instead of plain text. Once you save this change, you shouldn't have to restart the server before hitting
your browser and seeing the change immediately:
Optionally, you can also provide the proper MIME header as part of your response... in our case, we could
add: self.response.headers['Content-Type'] = 'text/html' as the first line of the handler’s get() method. Those of
you doing Option 2 have probably figured out that you can change the code directly in the Cloud Playground, hit the run
button and see the changes reflected immediately, like what you see below in Figure 4b.
https://sites.google.com/site/gdevelopercodelabs/app-engine/python-codelab?tmpl=%2Fsystem%2Fapp%2Ftemplates%2Fprint%2F&showPrintDialog=1
4/29/2018 https://sites.google.com/site/gdevelopercodelabs/app-engine/python-codelab?tmpl=%2Fsystem%2Fapp%2Ftemplates%2Fprint%2F…
Figure 4b. "Hello World!" with added H1 header tag in the Cloud Playground
For the remainder of the codelab, we'll just show the necessary bits for Option 1. Option 2 users can mimic the changes in
the code for the same effect as you can pretty much complete all of Part 1 using the Cloud Playground.
In this part of the codelab, we’re going to give your users the ability to input data. Rather than simply displaying “Hello
World,” let’s create a simple form with one text field and allow the user to submit it to your app. Part of the form is defining
the “action” for it, which is the URL that the browser will POST the form variables to. In this case, it’s /sign . However, since
we haven’t defined a handler for it, this request – when a user clicks on the “Sign Guestbook” button – will generate a 404
error, which you will either see in your browser, or it will be blank... it depends complete on your browser.
Leave the “Hello World” line as-is and add the form via another call to self.response.out.write() . Python’s triple
quotes (3 single or 3 double quotes) allows users to embed special characters like NEWLINEs directly into a string
verbatim. What does this mean for users? No more long string wrapping, awkward backslashes, or ugly string
concatentation. You can cut-n-paste blocks of HTML directly into the source. The indentation of the string data does not
matter, however, we indent it in a manner consistent with the Python code around it if nothing but for readbility.
To make this happen, add another call right below the "Hello World" output line:
self.response.write('''
<form action="/sign" method=post>
<input type=text name=content>
<input type=submit value="Sign Guestbook">
</form>
''')
You should now see the text field form and submit button now:
Again, there is no handler for guestbook signing yet, so you will get a 404 error or a blank screen if you click on the button!
Similar to the previous example, the changes here are so trivial that we do not need to show you the entire main.py file.
https://sites.google.com/site/gdevelopercodelabs/app-engine/python-codelab?tmpl=%2Fsystem%2Fapp%2Ftemplates%2Fprint%2F&showPrintDialog=1
4/29/2018 https://sites.google.com/site/gdevelopercodelabs/app-engine/python-codelab?tmpl=%2Fsystem%2Fapp%2Ftemplates%2Fprint%2F…
Obviously, the next step is to be able to process the user input. We will create a new handler to handle the /sign URL that
we created in the previous section of the codelab. We’ll call our new handler class GuestBook , although you’re free to use
any name you like. Forms use the HTTP POST method, so that’s why we’re creating a post() method (insetad of get() ).
Outside of a minor <br> tag added to the original form, you’ll be doing 2 things in this lab:
Adding the GuestBook class – watch the spelling... you don’t need to use CamelCasing as we’ve
done here but it should be consistent – and its post() method which simply outputs the user input.
Adding another 2-tuple when creating the application to define the handler ( GuestBook )
for /sign URLs
import webapp2
class MainHandler(webapp2.RequestHandler):
def get(self):
self.response.write('<h1>Hello world!</h1>')
self.response.write('''
<form action="/sign" method=post>
<input type=text name=content>
<br><input type=submit value="Sign Guestbook">
</form>
''')
class GuestBook(webapp2.RequestHandler):
def post(self):
self.response.write(
'<h2>You wrote:</h2> %s' % self.request.get('content')
)
app = webapp2.WSGIApplication([
('/', MainHandler),
('/sign', GuestBook),
], debug=True)
As you can see from the image below, the extra “<br>” caused the button to go below the form field.
When you submit the form, the “/sign” handler will display the output back to you:
One word of caution is that the app (at the moment) accepts all text entered by the user, including characters that make up
HTML tags, i.e., <, >, etc. If you want to escape these characters to avoid “injection” attacks, import the escape() function
from the Python standard library cgi module and wrap the content in it, i.e., from cgi import escape followed elsewhere
in your code with :
self.response.out.write('<h2>You wrote:</h2> %s' % escape(self.request.get('content')) .
https://sites.google.com/site/gdevelopercodelabs/app-engine/python-codelab?tmpl=%2Fsystem%2Fapp%2Ftemplates%2Fprint%2F&showPrintDialog=1
4/29/2018 https://sites.google.com/site/gdevelopercodelabs/app-engine/python-codelab?tmpl=%2Fsystem%2Fapp%2Ftemplates%2Fprint%2F…
GET / -> Show output -> fetch & display stored entries
Web applications wouldn’t nearly be as exciting if they didn’t store any data right? The App Engine datastore is arguably
the most complex component you will need to learn to become proficient at App Engine, but it is also the single thing that’s
the most difficult to implement if you were doing it yourself: creating a reliable, distributed, scalable, persistent data storage
mechanism. This is not a relational database with rows and tables... you cannot and should not think of it in this way. It is
more like an “object database” with “objects” or “entities.”
In this lab, we will store user input as the first major step at creating our online guestbook.
The home page behavior will also change... instead of just presenting a form for user input, we will show the previous
guestbook entries first as well as tweak the HTML a bit. So here are the changes that need to be made:
Demote “Hello World” by removing the header tag but leave it intact otherwise (more later)
Replace title with one that reads, “My GuestBook” – also add an <ol> tag to enumerate posts
Fetch all previous entries by calling the data model’s all() method
Loop through each entry and display to user
Terminate the <ol> list and change the text field to a multiline textarea
Once all those changes have been made, main.py should now look like:
class Greeting(db.Model):
content = db.StringProperty(multiline=True)
date = db.DateTimeProperty(auto_now_add=True)
class MainHandler(webapp2.RequestHandler):
def get(self):
self.response.write('Hello world!')
self.response.write('<h1>My GuestBook</h1><ol>')
#greetings = db.GqlQuery("SELECT * FROM Greeting")
greetings = Greeting.all()
for greeting in greetings:
self.response.write('<li> %s' % greeting.content)
self.response.write('''
</ol><hr>
<form action="/sign" method=post>
<textarea name=content rows=3 cols=60></textarea>
<br><input type=submit value="Sign Guestbook">
</form>
''')
class GuestBook(webapp2.RequestHandler):
def post(self):
greeting = Greeting()
greeting.content = self.request.get('content')
greeting.put()
self.redirect('/')
app = webapp2.WSGIApplication([
('/', MainHandler),
('/sign', GuestBook),
], debug=True)
Those of you who have a sharp eye will notice that one of the lines in get() is commented out. If you’re coming from a
relational world, you may feel more at home with App Engine’s GqlQuery interface, letting you use a subset of SQL to
issue the same request. You can use that line instead of the query used on line 18 but we don’t recommend that as you
https://sites.google.com/site/gdevelopercodelabs/app-engine/python-codelab?tmpl=%2Fsystem%2Fapp%2Ftemplates%2Fprint%2F&showPrintDialog=1
4/29/2018 https://sites.google.com/site/gdevelopercodelabs/app-engine/python-codelab?tmpl=%2Fsystem%2Fapp%2Ftemplates%2Fprint%2F…
need to start getting used to object queries anyway, and now is as good a time as any.
As mentioned above, the home page behavior is now different, and you should see those changes reflected below:
User authentication
Add author field to schema
At some point, application developers realize it’s a good thing to have unique users rather than treating everyone the
same. Google provides its own user authentication services as well as supports alternatives such as OAuth and
OpenID. The changes for this section of the lab are less intensive than the previous section.
Our example will use Google’s authentication service, which involves importing google.appengine.api.users (see line
4 above). The get_current_user() function is used to get the login of the current user. We need this in several places,
the first of which is the home page – we want to greet the currently-logged in user (lines 16-23). This call is also required
when a user submits an entry (lines 31-33) because this is the only time you can save this fact, and it’s pretty useful
information to differentiate who made which posts.
https://sites.google.com/site/gdevelopercodelabs/app-engine/python-codelab?tmpl=%2Fsystem%2Fapp%2Ftemplates%2Fprint%2F&showPrintDialog=1
4/29/2018 https://sites.google.com/site/gdevelopercodelabs/app-engine/python-codelab?tmpl=%2Fsystem%2Fapp%2Ftemplates%2Fprint%2F…
Along with the other changes, your updated main.py will now look like:
class Greeting(db.Model):
author = db.UserProperty()
content = db.StringProperty(multiline=True)
date = db.DateTimeProperty(auto_now_add=True)
class MainHandler(webapp2.RequestHandler):
def get(self):
user = users.get_current_user()
if user:
self.response.write('Hello %s' % user.nickname())
else:
self.response.write(
'Hello World! [<a href=%s>sign in</a>]' % \
users.create_login_url(self.request.uri)
)
self.response.write('<h1>My GuestBook</h1><ol>')
#greetings = db.GqlQuery("SELECT * FROM Greeting")
greetings = Greeting.all()
for greeting in greetings:
self.response.write('<li> %s' % greeting.content)
self.response.write('''
</ol><hr>
<form action="/sign" method=post>
<textarea name=content rows=3 cols=60></textarea>
<br><input type=submit value="Sign Guestbook">
</form>
''')
class GuestBook(webapp2.RequestHandler):
def post(self):
greeting = Greeting()
user = users.get_current_user()
if user:
greeting.author = user
greeting.content = self.request.get('content')
greeting.put()
self.redirect('/')
app = webapp2.WSGIApplication([
('/', MainHandler),
('/sign', GuestBook),
], debug=True)
Your app should function before as expected except for a couple of things... the first thing users will now notice is a sign-in
link at the top:
https://sites.google.com/site/gdevelopercodelabs/app-engine/python-codelab?tmpl=%2Fsystem%2Fapp%2Ftemplates%2Fprint%2F&showPrintDialog=1
4/29/2018 https://sites.google.com/site/gdevelopercodelabs/app-engine/python-codelab?tmpl=%2Fsystem%2Fapp%2Ftemplates%2Fprint%2F…
When users click to login, they should see a login prompt similar to this:
After logging in, they’re redirected back to the home page, which now shows user info:
This section of the codelab features a couple of minor yet interesting updates to the application. We’re requiring users be
logged in before being able to access the home page. Also, let’s change the query a bit... because a guestbook is a time-
https://sites.google.com/site/gdevelopercodelabs/app-engine/python-codelab?tmpl=%2Fsystem%2Fapp%2Ftemplates%2Fprint%2F&showPrintDialog=1
4/29/2018 https://sites.google.com/site/gdevelopercodelabs/app-engine/python-codelab?tmpl=%2Fsystem%2Fapp%2Ftemplates%2Fprint%2F…
sensitive application, let’s sort the entries to newest-first (reverse chronological) order.
Last time we added a login link; it’s only fair to add one for logout!
Redirect to the login link if not logged in
Sort by reverse order on the date and only show the ten newest posts
The “trickiest” code snippet is the customization of the query. We provide (onlines 18-21), both the change using the object
query as well as the GqlQuery so you can see the difference. Here is the resulting main.py :
class Greeting(db.Model):
author = db.UserProperty()
content = db.StringProperty(multiline=True)
date = db.DateTimeProperty(auto_now_add=True)
class MainHandler(webapp2.RequestHandler):
def get(self):
user = users.get_current_user()
if user:
self.response.write('Hello %s! [<a href=%s>sign out</a>]' % \
(user.nickname(), users.create_logout_url(self.request.uri)
))
else:
self.redirect(users.create_login_url(self.request.uri))
return
self.response.write('<h1>My GuestBook</h1><ol>')
#greetings = db.GqlQuery("SELECT * FROM Greeting ORDER BY date DESC LIMIT 10")
greetings = Greeting.all().order('-date').fetch(10)
for greeting in greetings:
self.response.write('<li> %s' % greeting.content)
self.response.write('''
</ol><hr>
<form action="/sign" method=post>
<textarea name=content rows=3 cols=60></textarea>
<br><input type=submit value="Sign Guestbook">
</form>
''')
class GuestBook(webapp2.RequestHandler):
def post(self):
greeting = Greeting()
user = users.get_current_user()
if user:
greeting.author = user
greeting.content = self.request.get('content')
greeting.put()
self.redirect('/')
app = webapp2.WSGIApplication([
('/', MainHandler),
('/sign', GuestBook),
], debug=True)
As far as behavior changes go, users will now see a logout link at the top:
https://sites.google.com/site/gdevelopercodelabs/app-engine/python-codelab?tmpl=%2Fsystem%2Fapp%2Ftemplates%2Fprint%2F&showPrintDialog=1
4/29/2018 https://sites.google.com/site/gdevelopercodelabs/app-engine/python-codelab?tmpl=%2Fsystem%2Fapp%2Ftemplates%2Fprint%2F…
There won’t be a login link any more because users who aren’t are automatically sent to the login
screen seen earlier in the previous codelab section. You can confirm this by just clicking on the
“sign out” link.
Web templates
Add templating to simplify display
Move all HTML out of code into template (index.html)
Update all variable access for template
Create template context dictionary
Render template w/filename & context
() not necessary for calling functions/methods
As your application grows both in terms of size, scope, and audience, the need for a visual designer increases. You wish
to have the UI be designed by... designers yet want the engineers to stay focused on getting the backend working. This is
where web templates come in. In this lab section, we move all of the HTML out into a web template.
App Engine supports a variety of templating systems, but those are separate downloads, and you would need to bundle
those with your app when you deploy live. However, the one from Django comes with App Engine and is available in both
the SDK as well as in production.
If you’re not familiar with templating systems, they are usually HTML combined with some sense of logic such
as if conditional statements and looping mechanisms like a traditional programming for loops. Django’s is no exception. In
order to pass the data from the application to the templating system, you need to create what is called a context, meaning
the variable namespace for the template itself. You'll see in the new code below and see how all of the HTML
(via self.response.out.write() calls) has been removed along with the relevant loops and conditionals. To summarize,
for this section of the codelab, the exact steps to take are:
Below is the (new) template file, which you can call index.html, that contains most of what was removed... we will add
anything else back later:
https://sites.google.com/site/gdevelopercodelabs/app-engine/python-codelab?tmpl=%2Fsystem%2Fapp%2Ftemplates%2Fprint%2F&showPrintDialog=1
4/29/2018 https://sites.google.com/site/gdevelopercodelabs/app-engine/python-codelab?tmpl=%2Fsystem%2Fapp%2Ftemplates%2Fprint%2F…
Although the syntax may be foreign to you newbies, you should get the gist of how the template works as well as the
syntax of the Django template flow control directives (called Django tags) which is somewhat Python-flavored. You should
also observe that callables in Django’s templating system do not need to be called (absence of calling parentheses: () )...
they use the pipe ( | ) symbol and are meant to be similar to Unix® pipes. Such callables are known as
Django filters and called automatically. You can learn more at the Django Built-in template tags and filters page as well as
the general Django template language page.
The remaining handler code after performing the steps outlined above looks like this:
Static files...
https://sites.google.com/site/gdevelopercodelabs/app-engine/python-codelab?tmpl=%2Fsystem%2Fapp%2Ftemplates%2Fprint%2F&showPrintDialog=1
4/29/2018 https://sites.google.com/site/gdevelopercodelabs/app-engine/python-codelab?tmpl=%2Fsystem%2Fapp%2Ftemplates%2Fprint%2F…
In the previous section, we started to logically segregate the files which are the responsibility of the engineer as well as the
visual designer. In this section, we take it one step further: static file support.
The user experience/interaction designer will want to do more than just creating web templates. Templates are just one
example of static files which are used to render web pages. Other static files include Cascading Style Sheets (CSS files)
and images, both of which can be used to give all the pages of a website a common look-and-feel.
To keep things organized, we create a top-level static directory containing subdirectories for CSS, HTML, and image
files. For our example, we’ll only do the first two. The template we created in the previous section is moved to
the static/html subdirectory while a brand new CSS file debuts in static/css . Here is what you need to do in this
section of the codelab:
In fact, the only changes you need to make in the handler code are:
class MainHandler(webapp2.RequestHandler):
def get(self):
user = users.get_current_user()
greetings = Greeting.all().order('-date').fetch(10)
context = {
'user': user,
'greetings': greetings,
'login': users.create_login_url(self.request.uri),
'logout': users.create_logout_url(self.request.uri),
}
tmpl = path.join(path.dirname(__file__), 'static/html/index.html')
self.response.write(render(tmpl, context))
class GuestBook(webapp2.RequestHandler):
def post(self):
greeting = Greeting()
user = users.get_current_user()
if user:
greeting.author = user
greeting.content = self.request.get('content')
greeting.put()
self.redirect('/')
app = webapp2.WSGIApplication([
('/', MainHandler),
('/sign', GuestBook),
], debug=True)
For the template, you can see below that we’ve added back the login and logout links, hence the reason why we required
the additional context variables above. There is also a reference to our new CSS file, main.css , as well. The other big
https://sites.google.com/site/gdevelopercodelabs/app-engine/python-codelab?tmpl=%2Fsystem%2Fapp%2Ftemplates%2Fprint%2F&showPrintDialog=1
4/29/2018 https://sites.google.com/site/gdevelopercodelabs/app-engine/python-codelab?tmpl=%2Fsystem%2Fapp%2Ftemplates%2Fprint%2F…
change is that instead of just enumerating and displaying the guestbook entries, we prepend each post with a timestamp
of when the post was made as well as the author (if known, or “anonymous” if not). Our guestbook is starting to look a little
more “professional” now!
<html><head>
<link type="text/css" rel="stylesheet" href="/static/css/main.css" />
</head>
<body>Hello
{% if user %}
{{ user.nickname }}!
[<a href="{{ logout }}"><b>sign out</b></a>]
{% else %}
World!
[<a href="{{ login }}"><b>sign in</b></a>]
{% endif %}
<hr>
<form action="/sign" method="post">
<textarea name="content" rows="3" cols="60"></textarea>
<br><input type="submit" value="Sign Guestbook">
</form>
</body></html>
The shortest addition in this section of the codelab is the CSS file (main.css) itself. It sets the default font and
background color and can be seen here:
body {
font-family: Verdana, Helvetica, sans-serif;
background-color: #DDDDDD;
}
The web template does make a call to the webserver to retrieve the CSS file. Your application does not have a handler
for /static/css so it needs to be added. Here is our new app.yaml file:
application: helloworld
version: 1
runtime: python
api_version: 1
handlers:
- url: /static/css
static_dir: static/css
- url: .*
script: main.py
These two lines need to be placed on top of your original ones because the latter handles all requests (via its universal
regular expression ".*"). You want the /static/css match to come first so it won't be "caught" by the universal one.
https://sites.google.com/site/gdevelopercodelabs/app-engine/python-codelab?tmpl=%2Fsystem%2Fapp%2Ftemplates%2Fprint%2F&showPrintDialog=1
4/29/2018 https://sites.google.com/site/gdevelopercodelabs/app-engine/python-codelab?tmpl=%2Fsystem%2Fapp%2Ftemplates%2Fprint%2F…
It’s time for a double feature: in this codelab section, we’re going to add caching using the App Engine Memcache API as
well as introduce template inheritance.
Fetching lots of data from the datastore can be time-consuming. Applications generally see a marked increase in
performance by caching often used and accessed data. Memcache is merely a hash table in the cloud, and comes both
with a simple API as well as more intermediate features that you expect. In our application, we’re going to cache the set of
guestbook entries. Specifically, we will cache them for ten seconds – this is sufficiently long enough so that you can
observe browser refreshes hitting the memcache but isn’t long enough to let the data in the cache go stale.
Another goal of this section further addresses the “common look-and-feel” theme we alluded to earlier. It is desireable that
web pages have the same general overall look along with the same header and footer links. It makes no sense to put the
same HTML in multiple HTML files when they can all share a “boilerplate” original. In our simple app, this comprises just
the “Hello World” greeting at the top along with login or logout link and the “import” of the CSS file – these would be shared
by all templates which extend from the base.
The complete handler which includes the changes from the previous as well as this section:
class Greeting(db.Model):
author = db.UserProperty()
content = db.StringProperty(multiline=True)
date = db.DateTimeProperty(auto_now_add=True)
class MainHandler(webapp2.RequestHandler):
def get(self):
user = users.get_current_user()
greetings = memcache.get('greetings')
if not greetings:
greetings = Greeting.all().order('-date').fetch(10)
memcache.add("greetings", greetings, 10)
context = {
'user': user,
'greetings': greetings,
'login': users.create_login_url(self.request.uri),
'logout': users.create_logout_url(self.request.uri),
}
tmpl = path.join(path.dirname(__file__), 'static/html/index.html')
self.response.write(render(tmpl, context))
class GuestBook(webapp2.RequestHandler):
def post(self):
greeting = Greeting()
user = users.get_current_user()
if user:
greeting.author = user
greeting.content = self.request.get('content')
greeting.put()
memcache.delete('greetings')
self.redirect('/')
https://sites.google.com/site/gdevelopercodelabs/app-engine/python-codelab?tmpl=%2Fsystem%2Fapp%2Ftemplates%2Fprint%2F&showPrintDialog=1
4/29/2018 https://sites.google.com/site/gdevelopercodelabs/app-engine/python-codelab?tmpl=%2Fsystem%2Fapp%2Ftemplates%2Fprint%2F…
app = webapp.WSGIApplication([
('/', MainHandler),
('/sign', GuestBook),
], debug=True)
Below is our new base template, base.html, which includes “Hello World” and the login or logout link plus the reference
to the CSS file:
<html><head>
<link type="text/css" rel="stylesheet" href="/static/css/main.css" />
</head>
<body>Hello
{% if user %}
{{ user.nickname }}!
[<a href="{{ logout }}"><b>sign out</b></a>]
{% else %}
World!
[<a href="{{ login }}"><b>sign in</b></a>]
{% endif %}
{% block content %}
{% endblock %}
</body></html>
In the base template above, you will see that there is room reserved for the actual content, e.g., in the Django block tag.
Below in the stripped page content file, index.html, we extend the base template and specify the content to drop into
the base template when rendered. Otherwise, it’s exactly the same as the template file from the previous codelab section:
{% extends "base.html" %}
{% block content %}
<h2>Top 10 Most Recent Guestbook Entries</h2>
<hr>
<form action="/sign" method="post">
<textarea name="content" rows="3" cols="60"></textarea>
<br><input type="submit" value="Sign Guestbook">
</form>
{% endblock %}
The background color has been removed from and styles added for HTML header, anchor, and paragraph tags in our
main.css file:
body {
font-family: Verdana, Helvetica, sans-serif;
color: #bdd;
background: #255;
padding: 0 5em;
margin: 0
}
h1 {
padding: 2em 1em;
background: #577
}
h2 {
color: #add;
border-top: 1px dotted #fff;
margin-top: 2em
}
https://sites.google.com/site/gdevelopercodelabs/app-engine/python-codelab?tmpl=%2Fsystem%2Fapp%2Ftemplates%2Fprint%2F&showPrintDialog=1
4/29/2018 https://sites.google.com/site/gdevelopercodelabs/app-engine/python-codelab?tmpl=%2Fsystem%2Fapp%2Ftemplates%2Fprint%2F…
a {
color: #9ff
}
p {
margin: 1em 0
}
In a more realistic situation, you would have a UE/UI/UX graphic designer working on the CSS independently of the
developer, allowing various team members to work on tasks simultaneously. The only file they may both access would be
the web template.
Once you’ve made the modifications above and have a running app, be sure to make a new entry into your datastore.
Then refresh your page several times and visit the memcache viewer on the admin console to confirm you’re hitting the
cache as well as fetching and re(caching) after 10s have elapsed. You can also confirm the cache is flushed as-expected
when a new post is made because the redirect to the home page should show the new post right away.
Other things you can do outside of the App Engine APIs include:
Twitter
PubSubHubBub
Google Wave bot
Sending email
Sending email is quite easy with App Engine. There are necessary restrictions, but it can be as simple as this code
snippet:
https://sites.google.com/site/gdevelopercodelabs/app-engine/python-codelab?tmpl=%2Fsystem%2Fapp%2Ftemplates%2Fprint%2F&showPrintDialog=1
4/29/2018 https://sites.google.com/site/gdevelopercodelabs/app-engine/python-codelab?tmpl=%2Fsystem%2Fapp%2Ftemplates%2Fprint%2F…
The development server will only log messages and not really send them unless you:
Specify an SMTP server (with login, passwd, etc.) or
Enable Sendmail on your local machine
http://code.google.com/appengine/docs/python/tools/devserver.html#Using_Mail
Be sure to substitute in the correct values for the sender and receive above before integrating into your application. What
is in the code snippet above will not compile.
App Engine also supports the receipt of email, but that is another topic for another time. Anyway, it should be fairly
straightforward to upgrade your guestbook by adding an email feature. Here are some ideas as to what you can do to your
app to enhance it with email:
For our guestbook example, we'll do the first item in the list above:
Email the administrator for every new post (supporting anonymous posts)
https://sites.google.com/site/gdevelopercodelabs/app-engine/python-codelab?tmpl=%2Fsystem%2Fapp%2Ftemplates%2Fprint%2F&showPrintDialog=1
4/29/2018 https://sites.google.com/site/gdevelopercodelabs/app-engine/python-codelab?tmpl=%2Fsystem%2Fapp%2Ftemplates%2Fprint%2F…
38 greeting.put()
39 memcache.delete('greetings')
40 mail.send_mail(
41 user and user.email() or 'postmaster@APP-ID.appspotmail.com', # from
42 'RECIP_EMAIL', # to
43 'GuestBook post from %s' % name, # subj
44 '%s wrote:\r\n\r\n"%s"' % (name, greeting.content), # body
45 )
46 self.redirect('/')
47
48 app = webapp.WSGIApplication([
49 ('/', MainHandler),
50 ('/sign', GuestBook),
51 ], debug=True)
52
53
54
55
56
57
Chat/XMPP/Instant Messaging
Another form of communication that people use these days is instant messaging. XMPP (Extensible Messaging and
Presence Protocol) is an open XML-based protocol (originally named Jabber) that enables this type of communication
between users (and chat robots)!
App Engine supports XMPP and has implemented 4 basic instant message (IM) operations:
Similar to email, activity is only logged via the development server. You really need to upload it live to experience this
functionality.
Sending IMs can be as simple as xmpp.send_message(jid, msg) where a “jid” is synonymous to a “Jabber ID” or the user
you’re trying to communicate with, and msg is a string representing the message itself. However, things aren’t always as
simple. You usually have to first check if a user is accepting your message, and if a user is online before sending an IM. In
order to do this, we define Subscriber class for tracking users' subscription/online status, and check is_friend and status
properties first.
Receiving of messages requires a separate handler, because when an IM comes in to your app, App Engine POSTS to
http://APP-ID.appspot.com/_ah/xmpp/message/chat/ , thus you have to handle those inbound requests. Likewise,
subscription notifications are sent to http://APP-ID.appspot.com/_ah/xmpp/subscription/SUBSCRIPTION_STATUS/ where
SUBSCRIPTION_STATUS is one of subscribe/subscribed/unsubscribe/unsubscribed, and online status
notifications are sent to http://APP-ID.appspot.com/_ah/xmpp/presence/ONLINE_STATUS where ONLINE_STATUS
is one of available/unavailable/probe(probe is for requesting user's online status, not a
notification). Please see more details at User Subscriptions and User Presence in our documentation.
In the application below, we’ve built both a sending and receiving of IMs. The sending occurs if a user is recognized as
being friend, and online; if the user is not a friend, an invitation to chat is sent instead, and if the user is a friend, but offline,
then nothing happens. On the receiving side, we simply capture the inbound message and immediately turn it around and
reply to the send that we received their message (and what it was).
import hashlib
from google.appengine.api import xmpp
from google.appengine.api import users
from google.appengine.ext import db
import webapp2
https://sites.google.com/site/gdevelopercodelabs/app-engine/python-codelab?tmpl=%2Fsystem%2Fapp%2Ftemplates%2Fprint%2F&showPrintDialog=1
4/29/2018 https://sites.google.com/site/gdevelopercodelabs/app-engine/python-codelab?tmpl=%2Fsystem%2Fapp%2Ftemplates%2Fprint%2F…
class Subscriber(db.Model):
address = db.StringProperty()
is_friend = db.BooleanProperty(default=False)
status = db.StringProperty(required=False)
@classmethod
def get_by_email(cls, email):
return cls.get_by_key_name(hashlib.sha1(email).hexdigest())
class MainHandler(webapp2.RequestHandler):
def get(self):
if users.get_current_user() is None:
self.redirect(users.create_login_url( self.request.uri))
return
user_address = users.get_current_user().email()
user = Subscriber.get_by_email(user_address)
if user and user.is_friend is True:
if user.status == "available":
xmpp.send_message(user_address, "test msg")
self.response.write("A message sent.")
else:
self.response.write("The user is offline.")
else:
xmpp.send_invite(user_address)
self.response.write("An invitation sent.")
class XMPPHandler(webapp2.RequestHandler):
def post(self):
msg = xmpp.Message(self.request.POST)
msg.reply("I got your msg: '%s'" % msg.body)
class XMPPPresenceHandler(webapp.RequestHandler):
def post(self, presence):
from_jid = self.request.get('from').split('/')[0]
def txn():
user = Subscriber.get_by_email(from_jid)
if user:
user.status = presence
return user.put()
else:
return Subscriber(key_name=hashlib.sha1( from_jid).hexdigest(),
address=from_jid, status=presence).put()
db.run_in_transaction(txn)
return
class XMPPSubscriptionHandler(webapp2.RequestHandler):
def post(self, subscription):
from_jid = self.request.get('from').split('/')[0]
is_friend = False
if subscription == "subscribe" or subscription == "subscribed":
is_friend = True
def txn():
user = Subscriber.get_by_email(from_jid)
if user:
user.is_friend = is_friend
return user.put()
else:
return Subscriber(key_name=hashlib.sha1(from_jid).hexdigest(),
address=from_jid, is_friend=is_friend).put()
db.run_in_transaction(txn)
return
app = webapp2.WSGIApplication([
('/_ah/xmpp/message/chat/', XMPPHandler),
('/_ah/xmpp/presence/(.*)/', XMPPPresenceHandler),
('/_ah/xmpp/subscription/(.*)/', XMPPSubscriptionHandler),
('/', MainHandler),
], debug=True)
Be sure to add inbound_services entries in your app.yaml file in order to activate those XMPP services. The new app.yaml
file will look like:
https://sites.google.com/site/gdevelopercodelabs/app-engine/python-codelab?tmpl=%2Fsystem%2Fapp%2Ftemplates%2Fprint%2F&showPrintDialog=1
4/29/2018 https://sites.google.com/site/gdevelopercodelabs/app-engine/python-codelab?tmpl=%2Fsystem%2Fapp%2Ftemplates%2Fprint%2F…
application: helloworld
version: 1
runtime: python27
api_version: 1
inbound_services:
- xmpp_message
- xmpp_presence
- xmpp_subscribe
handlers:
- url: /static/css
static_dir: static/css
- url: .*
script: main.py
All xmpp functions send_invite() , and send_message() also take a from_jid identifier. Similar to email, this can be
either APP-ID@appspot.com or xxx@APP-ID.appspotchat.com (yes, xxx can be anything and be sure to reread that
domain name!) If not provided to these functions, it defaults to APP-ID@appspot.com .
It’s also not inconceivable that we could add IM features to the guestbook application like the ones for email we suggested
above:
A simple “ping” could be sent to the guestbook owner notifying of a new entry
A user could add an entry to the guestbook by sending an IM to the “guestbook” app/user
Instant messaging is a popular and useful form of communication like email is, but unlike email, there is more of a factor of
instant gratification.
One good example of extending the simple XMPP app above as well as our GuestBook application is to add a “chatbot”
that takes a few simple commands, one of which returns the top 5 most recent guestbook entries:
https://sites.google.com/site/gdevelopercodelabs/app-engine/python-codelab?tmpl=%2Fsystem%2Fapp%2Ftemplates%2Fprint%2F&showPrintDialog=1
4/29/2018 https://sites.google.com/site/gdevelopercodelabs/app-engine/python-codelab?tmpl=%2Fsystem%2Fapp%2Ftemplates%2Fprint%2F…
42 greeting.put()
43 memcache.delete('greetings')
44 mail.send_mail(
45 user and user.email() or 'postmaster@APP-ID.appspotmail.com', # from
46 'RECIP_EMAIL', # to
47 'GuestBook post from %s' % name, # subj
48 '%s wrote:\r\n\r\n"%s"' % (name, greeting.content), # body
49 )
50 self.redirect('/')
51
52 class GBChatBot(webapp2.RequestHandler):
53 def post(self):
54 message = xmpp.Message(self.request.POST)
55 cmd = message.body[0:5].lower()
56 if cmd == '/list':
57 greetings = getGreetings(5)
58 reply = '%s\r\n\r\n%s' % (
59 '5 Most Recent Guestbook entries:',
60 '\r\n'.join(['%s: %s' % (
61 '*%s*' % g.author.nickname() if g.author else '_anonymous_',
62 g.content[:40]) for g in greetings
63
64 ]))
65 elif cmd == '/help':
66 reply = '''
67 Guestbook Chatbot 0.2
68 Supported commands are:
69 */list* (5 most recent entries)
70 */help* (this help msg)'''
71 else:
72 reply = '''
73 Command "%s" not supported!
74 Send "/help" for command list.''' % message.body
75 message.reply(reply)
76
77 app = webapp2.WSGIApplication([
78 ('/', MainHandler),
79 ('/sign', GuestBook),
80 ('/_ah/xmpp/message/chat/', GBChatBot),
81 ], debug=True)
82
83
84
85
86
87
The most notable new additions to this version of main.py are the new GBChatBot class and the creation of a separate
function to get the greetings we need to return to the user (5 for the chatbot and 10 for the website). Here are a summary
of all the changes we had to make from the previous version:
NOTE: the application at this point will not accept instant messages yet. You need to be invited to be able to chat with the
bot. To make this happen, just run the first XMPP app (the simple one before we integrated it into our GuestBook app). If
you hit that app at ‘/’, you will receive an invite. We will leave an exercise to the reader to add an opt-in feature to the
guestbook app, perhaps when users create a guestbook entry. Once the invite is sent, you can go to your XMPP
application and accept the chat request from your app/bot; then you will be able to send commands to it.
Part 3: Using other App Engine Services: UrlFetch, Cron, Channel API, Task
Queues
This third part is a stand alone module which will show you how to use the services:
The complete application these exercise build to is an application that fetches a news feed and then pushes the titles,
headlines and descriptions to browsers via the Channel API.
Channel API:
<script type="text/javascript" src="/_ah/channel/jsapi"></script>
To access the entire .html file used, just grab the file from the ZIP file above or jump over to the Java codelab momentarily.
This section will show how to extract content from an XML feed. In this example a news feed will be fetched and displayed.
A further exercise will fetch the feed and persist it in the datastore for later use.
The handler you should create -- we'll call ours FetchNewsHandler -- will fetch an XML news feed, parse the data, and
persist it in the datastore. Although it could be run manually, it is best suited to be run by a cron job as it will continually
refresh the datastore with the latest news feeds. These steps explain how to build this servlet.
https://sites.google.com/site/gdevelopercodelabs/app-engine/python-codelab?tmpl=%2Fsystem%2Fapp%2Ftemplates%2Fprint%2F&showPrintDialog=1
4/29/2018 https://sites.google.com/site/gdevelopercodelabs/app-engine/python-codelab?tmpl=%2Fsystem%2Fapp%2Ftemplates%2Fprint%2F…
This handler will respond to a JavaScript client and create a new Channel API Client ID and along with a token which is
then returned back to the client so it can set up a communication channel.
(For further documentation on the Channel API and setting up a Client ID and Tokens,
see http://code.google.com/appengine/docs/python/channel/overview.html)
create TokenHandler
auto-generate a "client ID"
somehow persist the client ID by using the datastore so the server can keep track of all connections
create a channel using the API
return the token
class TokenHandler(webapp2.RequestHandler):
def get(self):
. . .
This handler, will broadcast the headlines that have been persisted in the datastore to any browser clients. It first queries
the current clients id (that have also been persisted in the datastore), and then queries the news items. It then loops
through each news item and broadcasts to all clients.
Although this servlet can be run manually, it is best to be launched as an offline process using a task Queue. A final step
will show to make this servlet launchable via a task queue.
Break this out into a function if you want to run it as a deferred task.
class MessageHandler(webapp2.RequestHandler):
def get(self):
. . .
Installing and running the complete application can be done in order to see the entire set of services in this section working
together. These steps can be done even before doing the individual steps to build this application.
1. In the console, you should that http://localhost:8080 (or whatever port you started the dev is now accessible.
2. Important before accessing the application at the root directory, you must first seed the datastore with news feeds.
This is done by executing the following "cron" job manually
1. From the main page in your browser, click on "Login to view news client".
2. (Important) If you are signed in, click on "sign out" link. Do not click on "proceed to news client"
link.
3. Once you've signed out, click on the "Sign in" link and when the login page appears, select the "Sign in as
Administrator" check box, then click "Log In" button.
4. After logging in as an administrator, manually launch the cron job to fetch news feed data by
accessing: http://localhost:8080/cron/fetchNews
This will fetch the current news feed data and persist it into the local datastore for further use. You should
also see the data fetched displayed in the browser window.
5. Optional: Go to the local SDK Console and verify that the data has been persisted.
http://localhost:8080/_ah/admin/datastore
https://sites.google.com/site/gdevelopercodelabs/app-engine/python-codelab?tmpl=%2Fsystem%2Fapp%2Ftemplates%2Fprint%2F&showPrintDialog=1
4/29/2018 https://sites.google.com/site/gdevelopercodelabs/app-engine/python-codelab?tmpl=%2Fsystem%2Fapp%2Ftemplates%2Fprint%2F…
3. Now that the local datastore has been populated with new data, we can now access it from the application. Return
to the URL: http://localhost:8080
4. If you are not signed in, please sign in.
5. Upon verifying that you are signed in, click on the link "proceed to news client". (newsclient.html)
6. You should see the page "Breaking News Stories" as a header. You should also see browser Alert popup indicating
that you are now 'connected to the server'. - Click ok.
7. Now you can launch the Task that will start broadcasting out a series of news headlines to this browser. To launch
this task, open a new tab and access the url: http://localhost:8080/enqueueMsgs
This will launch a task queue that will start broadcasting messages to any clients that are currently logged in.
8. Now return to the new client page (newsclient.html) in the other tab.
9. You should now see news headlines and updates appearing in the browser window every 3 seconds!
The End
We hope that you’ve gotten a useful App Engine hands-on tutorial. There is plenty of documentation to read regarding the
existing online tutorial, and we have tried to add more specifics to the exercise of getting the Guestbook application
running.
https://sites.google.com/site/gdevelopercodelabs/app-engine/python-codelab?tmpl=%2Fsystem%2Fapp%2Ftemplates%2Fprint%2F&showPrintDialog=1