About the 1st Edition
The 1st edition of Programming Google App Engine was published by O'Reilly Media in November 2009. It is available in print, Kindle, PDF, ePub, Mobi, DAISY, and iPhone versions.
This edition is also available in Japanese.
Table of Contents
The 1st edition includes the following chapters:
- Chapter 1: Introducing Google App Engine
- Chapter 2: Creating an Application
- Chapter 3: Handling Web Requests
- Chapter 4: Datastore Entities
- Chapter 5: Datastore Queries
- Chapter 6: Datastore Transactions
- Chapter 7: Data Modeling with Python
- Chapter 8: The Java Persistence API
- Chapter 9: The Memory Cache
- Chapter 10: Fetching URLs and Web Resources
- Chapter 11: Sending and Receiving Mail and Instant Messages
- Chapter 12: Bulk Data Operations and Remote Access
- Chapter 13: Task Queues and Scheduled Tasks
- Chapter 14: The Django Web Application Framework
- Chapter 15: Deploying and Managing Applications
For a more detailed table of contents, see O'Reilly's page.
1st Edition Errata
As much as I wanted the 1st edition to be free of errors, several issues with the text were still in the draft when it was sent to press. For owners of the 1st edition in print, known errors are listed below. These errors have been fixed for the 2nd edition.
App Engine has changed quite a bit since the 1st edition was published. For a complete list of changes, see the App Engine release notes for Java and Python.
Many thanks to the readers that have submitted errata and feedback!
Chapter 2
- As of December 2009, the Google Plugin for Eclipse uses a default port of
8888
when running the Java development server. When the book went to print, it used the port8080
, so the book uses this port number throughout. Note that running the development server from Ant or from the command line still uses a default port of8080
, and this is consistent with Python's development server, so only Google Plugin users are affected by the change. - Example 2-16, the JPA data class example, uses the
EntityManager
methodpersist()
to save the object to the datastore. Instead, it ought to usemerge()
. This is because it calls this method in two cases: when the object is being made persistent for the first time, and when the object represents an existing entity that needs to be updated.persist()
works in the first case, but not the second; it throws anEntityExistsException
.merge()
works in both cases.- It's debatable whether this design is a good practice to begin with. If you keep the
EntityManager
open after fetching an object, all changes to the object are saved automatically when you call theEntityManager
'sclose()
method. Proper use of "attached" objects can result in clean designs with "automatic" persistence. As written, this example detaches objects from theEntityManager
immediately, and creates newEntityManager
instances for subsequent actions.
- It's debatable whether this design is a good practice to begin with. If you keep the
- Example 2-20, the Java Memcache example, accidentally used an unfinished version of the
UserPrefs.java
file for the print edition. The example shown does not match the text, and is incomplete. The correct example moves the cache setting code to a method namedcacheSet()
, and calls it from both thegetPrefsForUser()
andsave()
methods.
Chapter 3
- As of App Engine release 1.2.6, HTTPS is enabled (and optional) by default for all URL paths, in both Python and Java. You can use configuration to request different handling of HTTPS requests for specific paths, as described in the book.
- In "Authorization in Java," the example's
<web-resource-collection>
element is missing the required (but unused)<web-resource-name>
child element. Its value can be anything, but it must be present for theweb.xml
file to validate. - In the Java logging example, the first argument to
Logger.getLogger()
should beLoggingServlet.class.getName()
(notTestServlet
).
Chapter 4
- Example 4-1, creating an entity in Python using an "expando" class, uses the wrong value type for
author_birthdate
. The type should bedatetime.datetime
.datetime.date
is not a core datastore value type. You can use such a value with a declared property usingdb.DateProperty
. The property declaration converts between thedate
anddatetime
types automatically. Thedb.Model
example in this section shows adb.DateProperty
declaration. - In "Getting Entities Using Keys," the first Python example constructs the
Key
incorrectly. This should read:k = db.Key.from_path('Entity', 'alphabeta')
Chapter 5
- Figure 5-2 shows an excerpt of an index ordered by property "name," ascending. But the value "druidjane" appears above the value "dribbleman," which is the incorrect order.
- Several of the
GqlQuery
examples represent the GQL query as a single string spread across multiple lines for readability, but were printed without the line continuation markers required by Python. You can add the line continuation markers, put all lines of the GQL statement on one line, concatenate strings across multiple lines, or use a triple-quoted string ('''...'''
). - The text omits an example of using multi-valued properties with the low-level Java datastore API. This API accepts a
Collection
of a supported datastore value type and stores it in iterator order. When an entity with a multi-valued property is returned, its property has aList
value. - Figures 5-10 and 5-11 illustrate the example query with the inequality operator backwards:
WHERE charclass = "mage" AND level > 10
. The captions and text say:level < 10
. - Figure 5-14 illustrates an index that does not exactly match the code sample in the "MVPs and Equality Filters" section. The key in the last row should read "e1".
- The datastore API lets you perform kind-less queries to get entities of all kinds. As of release 1.2.6, the datastore keeps statistics in an app's datastore, and statistics entities are returned by kind-less queries. This is problematic for the Python API, which requires that all entities returned by the datastore have a corresponding Model class. Python apps can fix this by importing the statistics model classes even if the app doesn't otherwise use them, like so:
from google.appengine.ext.db import stats
Chapter 7
- The first example under "Queries and PolyModels" imports and uses the
PolyModel
class incorrectly. ThePolyModel
class is in a sub-package namedpolymodel
:from google.appengine.ext.db import polymodel class GameObject(polymodel.PolyModel): # ...
- In "Creating Your Own Property Classes," the examples illustrate how to create a custom value type, the
PlayerName
class, for data object attributes. ThePlayerNmaeProperty
declaration only considers such a value valid if thePlayerName.is_valid()
method returnsTrue
, which it only does if both arguments areunicode
values. Subsequent examples in this chapter passstr
literals to thePlayerName
constructor (e.g.'Ned'
) instead ofunicode
values (u'Ned'
). The simplest fix is to changeis_valid()
to acceptbasestring
values, like so:def is_valid(self): return (isinstance(self.first_name, basestring) and isinstance(self.surname, basestring) and len(self.surname) >= 6)
Alternatively, you can change all calls to thePlayerName
constructor to use Unicode string literals. - In "Customizing Default Values," the example of the
default_value()
method calls the superclass method incorrectly. The first line of the method should read as follows:default = super(PlayerNameProperty, self).default_value()
Chapter 8
- As of App Engine 1.3.0, there appears to be a bug in the JPA implementation that ignores the
NontransactionalWrite
setting in the JPA configuration (persistence.xml
). The intended behavior is, when this istrue
, attempts to create, update or delete an entity outside of an explicit JPA exception are performed in an implicit one-operation transaction. The actual behavior is as if this setting werefalse
, where all operations using a singleEntityManager
instance outside of an explicit transaction are performed in a single transaction. To create multiple entities in separate entity groups (such as with no entity group parent), you must do so using one explicit transaction each, or oneEntityManager
instance each. (Explicit transactions are a good practice anyway.)
This doesn't impact the book's text directly, but the book does not mention this bug. See issue 183 in the JDO/JPA project for more information and status. - In "Setting Up JPA," the definition of the
EMF
class unnecessarily includes the line:package clock;
This is how it was done in Chapter 2, so technically it's not an error, but it's confusing to see it here. In your code, set the package name to something appropriate to your app. - At the end of the section "Entities and Keys," the example of a string-encoded key value uses an incorrect type for the
id
field. It should beString
(notKey
). The annotations are correct. - In "Saving, Fetching, and Deleting Objects," the example presumes the
emf
variable is set to an instance of theEntityManagerFactory
, but no example in the chapter explicitly illustrates how to get this instance. The intent is to get this instance from the static methodEMF.get()
, whereEMF
is a class defined earlier in the chapter. The full usage is as follows:import javax.persistence.EntityManagerFactory; import clock.EMF; // where "clock" is wherever you put the EMF class // ... EntityManagerFactory emf = EMF.get(); EntityManager em = null; try { em = emf.createEntityManager(); // ... } finally { em.close(); }
- The JPQL
asIterable()
method returns the results as an iterable. This iterable is limited to 1,000 results, and does not return results indefinitely as is implied by the text. - In "Queries and JPQL," the example showing JPQL field selection returning
List<Object[]>
neglects to cast the values of eachObject[]
. All three attempts to access the array should cast toString
:for (Object[] result : results) { String isbn = (String) result[0]; String title = (String) result[1]; String author = (String) result[2]; // ... }
- The book states that JPQL's
DELETE
command is supported, but does not mention how to execute a non-SELECT
query. To do this, call theQuery
object'sexecuteUpdate()
method. - In "Relationships," the example of a
@OneToMany
relationship shows the@ManyToOne
annotation with amappedBy
argument. This argument actually belongs on the@OneToMany
side of the relationship, with no arguments to the corresponding@ManyToOne
argument:public class Book { // ... @OneToMany(cascade=CascadeType.ALL, mappedBy="book") private List
Note that thebookReviews = null; } mappedBy
argument still belongs in theBookCoverImage
class in the@OneToOne
example. With a@OneToMany
, the "one" side is the parent entity of the "many" and is created first. With a@OneToOne
, the side with themappedBy
argument becomes the child—which is inconsistent with the@OneToMany
behavior. It's important to know which side of a relationship becomes the entity group parent to make sure the groups work out consistently.
Chapter 10
- Example 10-4 is missing import statements for
com.google.appengine.api.urlfetch.HTTPRequest
andHTTPMethod
.
Chapter 11
- In Example 11-1, the call to the
EmailMessage
constructor uses an incorrectly formattedsender
.<
and>
should be<
and>
, respectively. The full constructor call should look like this:message = mail.EmailMessage( sender='The Example Team <admin@example.com>', to=user_addr, subject='Your Example Registration Key', body=message_body, html=html_message_body, attachments=[('example_key.txt', software_key_data)]
- In the second code sample in the section "Sending Email in Java," the code that adds the plaintext MIME body part is incorrect. The first line that reads:
multipart.addBodyPart(htmlPart);
should read:multipart.addBodyPart(textPart);
- Since the book was printed, additional content types have been added to the list of allowed types for email attachments. See the complete list.
- In "Sending Email in Java," the first example uses string concatenation across multiple lines to build an outgoing message. The first two string parts need to end with a space to prevent the final string from having words run together.
Chapter 12
- In "Using the Remote API from a Script," in the example, all occurrences of
APP-ID
should readAPP_ID
. Similarly, all occurrences ofapp-id
should readapp_id
.
Chapter 13
- Since App Engine release 1.2.8, task queues consider any
2xx
HTTP response code to be the successful completion of a task, not just200
. - Since App Engine release 1.2.8, you can purge tasks from queues using the Administration Console.
- Since App Engine release 1.2.8, the Java development server executes tasks automatically in a separate thread. You can disable automatic thread execution by setting the JVM flag
task_queue.disable_auto_task_execution=true
(see the official documentation). The Python development server does not execute tasks automatically. - Since App Engine release 1.2.8, the default URL for the default queue is
/_ah/queue/default
, not/_ah/queue
as indicated in the book. (Previous experimental releases used/_ah/queue
as the URL.)
Chapter 14
- If you're following along with the book's example of using the Django App Engine Helper, be sure to use release "r100" (December 2009) or later. Previous versions had a bug involving test fixtures and date properties. (The book's example is correct.)
Chapter 15
- The text says the
appcfg.py request_logs
command fetches logs for all major versions of the app. This is incorrect. By default, the command fetches logs for the major version mentioned in the app configuration. You can request logs for a specific major version of the app by providing the--version
argument to the command.