There has been some discussion on the app engine list recently about the best way to handle data migration when your model changes. I thought I'd put together a short summary of some of the options which are open to add, rename, remove or change the type of an existing field. For the examples here, I'm going to start with a simple model example and gradually change the class. At each stage we can see how the data store handles the change.
We also want some test data. I think one name should be enough here.
Adding a fieldAdding a new field is easy enough, we get it set to the default value
Watch out though, until you commit an entity the new field won't be
Renaming a fieldHow about renaming a field? There is of course no relationship between
To rename a property (in this example birth_date becomes yob), create
The actual renaming is done by picking up the old and new names as
To update the datastore just grab a bunch of objects which have the
Once the migration has been completed we can remove the DummyProperty If the old field isn't a property which supports sorting then you'll Removing a fieldRemoving a field is not a problem, the extra data in the database is
Alternatively we can make it into a dummy property and use the same
Changing field typeIf we need to change the type of a property and don't try to handle
Fortunately that can be fixed using __init__ in the same way as a
You can also use this to fixup bad content: if something gets into the |
Wednesday, 28 May 2008
Migrating models
Posted by
Duncan Booth
at
17:46
1 comments
Labels: Google App Engine
Wednesday, 14 May 2008
Decoding a Query back to a string
On the App Engine Google group, Thomas Kuczek asked: I have a query object representing a query. How can I print the resulting Gql to log it with the logger framework? I thought this sounded like an interesting and possibly useful thing to do, so I wrote a small module which can convert either a db.Query or a db.GqlQuery object to a meaningful string. Warning This code depends on details of the query implementation (a lot of the required fields are private), so I can be fairly safe in saying that not only may it break in the next release of the App Engine environment, but it almost certainly will break. In the code samples which follow the test class is: class Person(db.Model): First some setup code for the tests: >>> from google.appengine.ext import db Now try printing some queries: >>> q = Person.all() If we have more than one filter, showQuery will always output them in sorted order rather than the order in which they were input. This is simply to make doctests easier: >>> print showQuery(q.filter('height <', 72)) We can even handle an ancestor on the query although the key may be a bit of a mouthful: >>> p = Person(first_name='Duncan', last_name='Booth', city='Oxford', height=183) There is also a showGqlQuery function to convert GQL back to the equivalent query: >>> q = db.GqlQuery("SELECT * FROM Person WHERE last_name = :1 AND height < :2", "Smith", 72) Notice that once again the output may have the clauses in a different order than they were originally input. >>> print showGqlQuery(db.GqlQuery("SELECT * FROM Person WHERE last_name = :name AND height < :height")) We can also handle literal values in queries: >>> print showGqlQuery(db.GqlQuery("SELECT * FROM Person WHERE last_name = 'Smith'")) Sorting is also handled. The ORDER BY clause does preserve the original order: >>> print showGqlQuery(db.GqlQuery("SELECT * FROM Person WHERE height < :1 ORDER BY last_name ASC")) Ancestor, limit, and offset classes also all work. If you specify limit and offset separately then they are output together: >>> print showGqlQuery(db.GqlQuery("SELECT * FROM Person WHERE ANCESTOR IS :1 AND height < 72")) The source code (showquery.py) looks like this: from google.appengine.ext import db |
Posted by
Duncan Booth
at
20:21
2
comments
Labels: Google App Engine
Monday, 5 May 2008
Unit testing with Google App Engine
The App Engine isn't too friendly for testing: you can't use interactive mode, and there's some setup needed to get a test harness. I've found that using doctest works quite nicely with the appengine, provided you get that initial setup out of the way. If your application interacts with the datastore, then you probably want to set up some objects before querying them and to my mind that sits better with the doctest model of a long chatty document describing a pseudo-interactive session than the xUnit style of separate tests each starting from a clean system doing something and making a single assertion about the resulting state. For a particular project you can update the test framework to perform common setup for the tests. The structure I've come up with sets up the environment to run tests under the appEngine framework. It looks for tests in two places: *.py files in the project directory, and tests/*.tests. For the Python files doctest will run tests from all docstrings found in the file (and you must have at least one docstring in every Python source file otherwise you'll get an error, but you don't have to actually have any tests in it). Each docstring forms a separate test. For the other files they are just plain text (or you can use reST) and the entire file is run as one test (so the state is preserved until the end of the file). Here's a sample doctest file: DateTime testing
Download an example appengine project with this test and execute tests/runtests.py to run it. |
Posted by
Duncan Booth
at
10:39
4
comments
Labels: Google App Engine