<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" media="screen" href="/~d/styles/atom10full.xsl"?><?xml-stylesheet type="text/css" media="screen" href="http://feeds.feedburner.com/~d/styles/itemcontent.css"?><feed xmlns="http://www.w3.org/2005/Atom" xmlns:openSearch="http://a9.com/-/spec/opensearch/1.1/" xmlns:georss="http://www.georss.org/georss" xmlns:gd="http://schemas.google.com/g/2005" xmlns:thr="http://purl.org/syndication/thread/1.0" xmlns:feedburner="http://rssnamespace.org/feedburner/ext/1.0" gd:etag="W/&quot;A0EDQXkzfCp7ImA9WhRaE0o.&quot;"><id>tag:blogger.com,1999:blog-9174832840890796616</id><updated>2012-02-16T00:07:50.784-08:00</updated><category term="space" /><category term="Python" /><category term="support" /><category term="html5" /><category term="Google Docs" /><category term="GAE Datastore" /><category term="Windows7" /><category term="Google Sites" /><category term="he3-appengine-lib" /><category term="mapreduce" /><category term="test" /><category term="JQuery" /><category term="Picasa" /><category term="Django Templates" /><category term="Google Gadgets" /><category term="css" /><category term="Git" /><category term="PyDev" /><category term="Mac OS" /><category term="Apache Ant" /><category term="PyLint" /><category term="JSON" /><category term="mockups" /><category term="wireframes" /><category term="simulation" /><category term="Book Review" /><category term="operating systems" /><category term="Google Reader" /><category term="Closure Compiler" /><category term="Subversion" /><category term="security" /><category term="vmware" /><category term="Google App Script" /><category term="Design" /><category term="Open Social" /><category term="Blogger" /><category term="blog" /><category term="Google" /><category term="Google App Engine" /><category term="OpenID" /><category term="Lynda.com" /><category term="haiku" /><category term="SetupTools" /><category term="Eclipse" /><category term="generations" /><category term="Tools" /><category term="Subclipse" /><category term="Speed" /><category term="Ubuntu" /><category term="JavaScript" /><category term="virtual machines" /><category term="ColdFusion" /><category term="Google Apps" /><category term="Aptana Studio" /><title>Learning Technical Stuff</title><subtitle type="html">This blog is a record of me learning stuff.I hope that in the process of recording and expressing technical stuff I will learn it better. What is technical stuff? Mostly development and computer programming, networking and other geeky tips. Enjoy!</subtitle><link rel="http://schemas.google.com/g/2005#feed" type="application/atom+xml" href="http://www.learningtechnicalstuff.com/feeds/posts/default" /><link rel="alternate" type="text/html" href="http://www.learningtechnicalstuff.com/" /><link rel="next" type="application/atom+xml" href="http://www.blogger.com/feeds/9174832840890796616/posts/default?start-index=26&amp;max-results=25&amp;redirect=false&amp;v=2" /><author><name>Ben Davies</name><uri>http://www.blogger.com/profile/15225977828752157616</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="29" height="32" src="http://4.bp.blogspot.com/_qLhNkiMU1-k/Sh_l4FTCGkI/AAAAAAAAAEY/ABoYTtyFXEA/S220/IMG_2414.jpg" /></author><generator version="7.00" uri="http://www.blogger.com">Blogger</generator><openSearch:totalResults>81</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>25</openSearch:itemsPerPage><atom10:link xmlns:atom10="http://www.w3.org/2005/Atom" rel="self" type="application/atom+xml" href="http://feeds.feedburner.com/LearningTechnicalStuff" /><feedburner:info uri="learningtechnicalstuff" /><atom10:link xmlns:atom10="http://www.w3.org/2005/Atom" rel="hub" href="http://pubsubhubbub.appspot.com/" /><entry gd:etag="W/&quot;CE8BSX06eSp7ImA9WhZbGE4.&quot;"><id>tag:blogger.com,1999:blog-9174832840890796616.post-7866278089108094581</id><published>2011-06-23T05:37:00.000-07:00</published><updated>2011-06-23T05:40:58.311-07:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-06-23T05:40:58.311-07:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="Mac OS" /><category scheme="http://www.blogger.com/atom/ns#" term="PyLint" /><category scheme="http://www.blogger.com/atom/ns#" term="Aptana Studio" /><category scheme="http://www.blogger.com/atom/ns#" term="SetupTools" /><category scheme="http://www.blogger.com/atom/ns#" term="Python" /><title>Installing Pylint for Python 2.5 on Mac</title><content type="html">In a &lt;a href="http://www.learningtechnicalstuff.com/2011/06/getting-started-with-google-app-engine.html"&gt;previous post&lt;/a&gt; I discussed how to set up Google App Engine (Python) development environment on Mac OS X 10.6. I'm liberally mining some of &lt;a href="http://www.learningtechnicalstuff.com/2010/10/installing-python-google-appengine-sdk.html#more"&gt;my own notes&lt;/a&gt; on setting up Google App Engine on Ubuntu 10.10.&lt;br /&gt;
&lt;br /&gt;
In my previous Ubuntu environment I had a neat Ant script for building, testing and uploading my Google App Engine applications such as My Web Brain. One of tests the script performed was Python code style checks using &lt;a href="http://www.logilab.org/857"&gt;Pylint&lt;/a&gt;, which I have also mentioned before in the context of Ubuntu. I've set this up again on my Mac, so I thought I thought I would post my notes here.&lt;br /&gt;
&lt;br /&gt;
In this post I talk about:&lt;br /&gt;
&lt;br /&gt;
&lt;ul&gt;&lt;li&gt;Installing Setup Tools for Python 2.5,&lt;/li&gt;
&lt;li&gt;Installing PyLint using SetupTools, and&lt;/li&gt;
&lt;li&gt;Configuring Aptana/Pydev to use PyLint&lt;/li&gt;
&lt;/ul&gt;Lets go!&lt;br /&gt;
&lt;a name='more'&gt;&lt;/a&gt;&lt;br /&gt;
A couple of pre-requisites first: You need to have &lt;a href="http://support.apple.com/kb/ht1528"&gt;enabled the root user&lt;/a&gt; to be able to Sudo and I'm assuming you have already installed Python 2.5 (see my &lt;a href="http://www.learningtechnicalstuff.com/2010/10/installing-python-google-appengine-sdk.html#more"&gt;previous post&lt;/a&gt; for more information on that)&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;Installing SetupTools&lt;/b&gt;&lt;br /&gt;
&lt;br /&gt;
I still prefer to install Pylint via &lt;a href="http://pypi.python.org/pypi/setuptools"&gt;SetupTools&lt;/a&gt;. SetupTools makes the &lt;code&gt;easy_install&lt;/code&gt; command available from the shell to automatically install python eggs and sort out their dependencies. You might already have SetupTools on your system, but do you have the Python 2.5 version? I assume it is important (it does after all need to install packages into the correct site-packages folder). To install SetupTools, first download the egg file:&lt;br /&gt;
&lt;br /&gt;
&lt;a href="http://pypi.python.org/packages/2.5/s/setuptools/setuptools-0.6c11-py2.5.egg"&gt;http://pypi.python.org/packages/2.5/s/setuptools/setuptools-0.6c11-py2.5.egg&lt;/a&gt;&lt;br /&gt;
&lt;br /&gt;
Open a terminal session and cd into the folder you saved it into. Type the following into the commandline:&lt;br /&gt;
&lt;br /&gt;
&lt;code&gt;$ sudo sh setuptools-0.6c11-py2.5.egg&lt;/code&gt;&lt;br /&gt;
&lt;br /&gt;
This will install setup tools. When you run easy_install, make sure to use the command easy_install-2.5 to make sure you are addressing the correct Python installation on your system.&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;Installing PyLint&amp;nbsp;&lt;/b&gt;&lt;br /&gt;
&lt;br /&gt;
Type the following into your terminal shell:&lt;br /&gt;
&lt;br /&gt;
&lt;code&gt;$ sudo easy_install-2.5 pylint&lt;/code&gt;&lt;br /&gt;
&lt;br /&gt;
This will install PyLint and all of its dependencies. You can now run PyLint from the command line (or from an Ant Task).&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;Using PyLint in Aptana&lt;/b&gt;&lt;br /&gt;
&lt;br /&gt;
Aptana (now incorporating PyDev - again see my previous post) can integrate with PyLint to give you feedback on code style as you work and make changes to your source files. To configure Aptana to do this, open Preferences -&amp;gt; Pydev -&amp;gt; PyLint, check the the &lt;i&gt;Use PyLint&lt;/i&gt; option and locate your Pylint executable. Despite the hint to look for a file named &lt;code&gt;lint.py&lt;/code&gt;, I've never found such a file.&lt;br /&gt;
&lt;br /&gt;
Instead, find the pylint binary. If you installed PyLint as per the above the following path should be where it is at:&lt;br /&gt;
&lt;br /&gt;
&lt;code&gt;/Library/Frameworks/Python.framework/Versions/2.5/bin/pylint&lt;/code&gt;&lt;br /&gt;
&lt;br /&gt;
Click OK, open a python file and make some changes. Warnings and errors from PyLint should start appearing in the Problems view (Window -&amp;gt; Show View -&amp;gt; Problems if you can't see it). You can also specify a preferences file in the preferences page for PyLint to pass in any global settings (to ignore certain style rules, for example).&lt;br /&gt;
&lt;br /&gt;
There you go. PyLint is installed and ready to use from the command line, an Ant Task or in Aptana. Let me know in the comments if you can suggest a better approach, have a question or any sort of general comment.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9174832840890796616-7866278089108094581?l=www.learningtechnicalstuff.com' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/LearningTechnicalStuff/~4/JJvUBcu_Evs" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://www.learningtechnicalstuff.com/feeds/7866278089108094581/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.learningtechnicalstuff.com/2011/06/installing-pylint-for-python-25-on-mac.html#comment-form" title="1 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/9174832840890796616/posts/default/7866278089108094581?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/9174832840890796616/posts/default/7866278089108094581?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/LearningTechnicalStuff/~3/JJvUBcu_Evs/installing-pylint-for-python-25-on-mac.html" title="Installing Pylint for Python 2.5 on Mac" /><author><name>Ben Davies</name><uri>http://www.blogger.com/profile/15225977828752157616</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="29" height="32" src="http://4.bp.blogspot.com/_qLhNkiMU1-k/Sh_l4FTCGkI/AAAAAAAAAEY/ABoYTtyFXEA/S220/IMG_2414.jpg" /></author><thr:total>1</thr:total><feedburner:origLink>http://www.learningtechnicalstuff.com/2011/06/installing-pylint-for-python-25-on-mac.html</feedburner:origLink></entry><entry gd:etag="W/&quot;AkcMQ3w-eip7ImA9WhZbFEU.&quot;"><id>tag:blogger.com,1999:blog-9174832840890796616.post-2632021574273805055</id><published>2011-06-19T05:54:00.000-07:00</published><updated>2011-06-19T05:54:42.252-07:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-06-19T05:54:42.252-07:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="Mac OS" /><category scheme="http://www.blogger.com/atom/ns#" term="Google App Engine" /><category scheme="http://www.blogger.com/atom/ns#" term="Aptana Studio" /><category scheme="http://www.blogger.com/atom/ns#" term="Python" /><title>Getting Started with Google App Engine, Python 2.5 and PyDev on OS X 10.6</title><content type="html">Last week my shiny new Apple iMac 27 inch arrived and I thought I would share my steps in getting my &lt;a href="http://code.google.com/appengine/"&gt;Google App Engine&lt;/a&gt; (Python) environment set up. I have previously written about setting up Ubuntu 10.10 for the same purpose and that post remains one of the most popular on this blog.&lt;br /&gt;
&lt;br /&gt;
To set up our Google App Engine environment we will need the correct version of Python, the Google App Engine SDK and a Python/GAE friendly IDE.&lt;br /&gt;
&lt;br /&gt;
&lt;a name='more'&gt;&lt;/a&gt;&lt;br /&gt;
&lt;b&gt;Installing Python 2.5&lt;/b&gt;&lt;br /&gt;
&lt;br /&gt;
Firstly comes Python itself. Despite rumours of eventually upgrading the Python version supported on Google App Engine to 2.7, Google App Engine for Python still only supports Python 2.5. Mac OS X 10.6 ships with a distribution of Python 2.6. For hassle-free Google App Engine development it is always best to stick with the supported version.&lt;br /&gt;
&lt;br /&gt;
Finding a packaged distribution of Python 2.5 for Mac is more difficult than it was the last time I looked, but you can still find a package &lt;a href="http://pythonmac.org/packages/py25-fat/index.html"&gt;here&lt;/a&gt; at &lt;a href="http://pythonmac.org/"&gt;pythonmac.org&lt;/a&gt;. Download the package and install it by running the installer. If you open a command prompt and type &lt;code&gt;python --version&lt;/code&gt; you should see Python 2.5 is now the default version.&lt;br /&gt;
&lt;br /&gt;
What happened to the existing bundled Python distribution? (or any others, if installed?) They are still present - the Python executable found by the shell is the one under /Library/Frameworks/Python.framework/Versions/Current, where &lt;i&gt;Current&lt;/i&gt; is a symbolic link to a specific version's folder. Remember this if you ever need the shell to find a different version.&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;Getting the Google App Engine SDK&lt;/b&gt;&lt;br /&gt;
&lt;br /&gt;
The Google App Engine SDK is easy to find, but the &lt;a href="http://code.google.com/appengine/downloads.html"&gt;Google App Engine SDK download page&lt;/a&gt; is a touch misleading. In the download the links for the SDK, the link offered for Mac OS X is for the Google App Engine launcher. Contrary to my original assessment this download does in fact contain the SDK. When you finish downloading the package, open it and drag the launcher file to your applications folder. When you first launch it, the program will offer to create the necessary symbolic links in your &lt;i&gt;/usr/local/bin &lt;/i&gt;folder.&lt;br /&gt;
&lt;br /&gt;
If you would like more control over how and where the SDK is installed, you can grab the &lt;i&gt;Linux/Other Platforms&lt;/i&gt; archive instead.&lt;br /&gt;
&lt;br /&gt;
I never use the graphical launcher itself; I tend to start and upload my local App Engine programs via my IDE or an Ant build script (which is probably worth a post).&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;Setting up an IDE&lt;/b&gt;&lt;br /&gt;
&lt;br /&gt;
I've &lt;a href="http://www.learningtechnicalstuff.com/search/label/PyDev"&gt;previously posted&lt;/a&gt; about PyDev for Eclipse being my Python IDE of choice for Google App Engine and this has not changed. Fortunately getting it set up is now easier - PyDev is now a bundled component of &lt;a href="http://www.aptana.com/"&gt;Aptana Studio 3&lt;/a&gt;. Aptana Studio is built on the open source &lt;a href="http://www.eclipse.org/"&gt;Eclipse IDE&lt;/a&gt;, and can either be installed as a standalone application, with all the extensibility of Eclipse, or added as a plugin to an existing Eclipse installation. Download the standalone version or find the details of required update site for a plugin installation &lt;a href="http://www.aptana.com/products/studio3/download"&gt;here&lt;/a&gt;. The nice thing about Aptana for Google App Engine development is the inclusion of support for general web technologies and specific support for Django Templates.&lt;br /&gt;
&lt;br /&gt;
You can also optionally install the &lt;a href="http://code.google.com/eclipse/"&gt;Google Plugin for Eclipse&lt;/a&gt;. The vast bulk of this plugin is dedicated to assisting Google Web Toolkit development with Java, however, and out of the box Aptana seem to have included some or all of the limited Python/Google App Engine features in their studio, &amp;nbsp;such as a way to launch the development server and a project wizard for setting up a Google App Engine project. (&amp;nbsp;To install the plugin, visit the &lt;a href="http://code.google.com/eclipse/docs/getting_started.html"&gt;installation page&lt;/a&gt;. Note that if you installed Aptana Studio as a standalone install you might need to satisfy some other Eclipse dependencies not included in the Aptana Eclipse distribution. I'm skipping the installation of the plugin at the moment.)&lt;br /&gt;
&lt;br /&gt;
Within Aptana (or Eclipse), open the Preferences and add the Python 2.5 interpreter (Aptana -&amp;gt; Preferences -&amp;gt; PyDev -&amp;gt; Interpreter - Python). Click &lt;i&gt;New&lt;/i&gt;&amp;nbsp;and enter a name (eg. 'Python 2.5') and the path to Python 2.5. This will be the specific 2.5 path under /Library/Frameworks/Python.framework/versions/2.5/bin. Aptana/Eclipse will successfully find a range of folders to add to the PythonPath for that interpreter.&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;Starting a New Google App Engine Project&lt;/b&gt;&lt;br /&gt;
&lt;br /&gt;
As mentioned, Aptana Studio now ships with a Google App Engine project wizard. This is helpful but not necessary - the most critical work to be done in making a Python project a Google App Engine project is to correctly add the SDK libraries to the Python Path recognised by the IDE. Doing this manually is described &lt;a href="http://code.google.com/appengine/articles/eclipse.html"&gt;here&lt;/a&gt;. &amp;nbsp;To use the project wizard instead, select &lt;i&gt;File -&amp;gt; New -&amp;gt; PyDev -&amp;gt; Google App Engine project&lt;/i&gt; to create a new project. Make sure to change the Grammar and the Python Interpreter both to Python version 2.5.&lt;br /&gt;
&lt;br /&gt;
You will be then be asked to locate the Google App Engine SDK files. If you installed the launcher application, the SDK files are actually within the installed .app file (folder?), which might make finding these files in Finder difficult. These easiest way to track down this path is through the terminal. Find where the dev_appserver.py symbolic link is (via which dev_appserver.py) and then inspect where it is pointing (ls -l usr/local/bin/dev_appserver.py). Grab this path from the terminal and paste it into the Aptana/Eclipse prompt.&lt;br /&gt;
&lt;br /&gt;
You will then be prompted to confirm the folders to add to the Python path, with your only decision about which version of Django templates you will be using.&lt;br /&gt;
&lt;br /&gt;
The new project wizard will close, and your new Google App Engine project will be ready.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9174832840890796616-2632021574273805055?l=www.learningtechnicalstuff.com' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/LearningTechnicalStuff/~4/U2Do6hB8G-M" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://www.learningtechnicalstuff.com/feeds/2632021574273805055/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.learningtechnicalstuff.com/2011/06/getting-started-with-google-app-engine.html#comment-form" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/9174832840890796616/posts/default/2632021574273805055?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/9174832840890796616/posts/default/2632021574273805055?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/LearningTechnicalStuff/~3/U2Do6hB8G-M/getting-started-with-google-app-engine.html" title="Getting Started with Google App Engine, Python 2.5 and PyDev on OS X 10.6" /><author><name>Ben Davies</name><uri>http://www.blogger.com/profile/15225977828752157616</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="29" height="32" src="http://4.bp.blogspot.com/_qLhNkiMU1-k/Sh_l4FTCGkI/AAAAAAAAAEY/ABoYTtyFXEA/S220/IMG_2414.jpg" /></author><thr:total>0</thr:total><feedburner:origLink>http://www.learningtechnicalstuff.com/2011/06/getting-started-with-google-app-engine.html</feedburner:origLink></entry><entry gd:etag="W/&quot;Ak8MRHY6cCp7ImA9WhZUE0s.&quot;"><id>tag:blogger.com,1999:blog-9174832840890796616.post-6209693899299268001</id><published>2011-06-06T07:01:00.000-07:00</published><updated>2011-06-06T07:01:25.818-07:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-06-06T07:01:25.818-07:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="generations" /><category scheme="http://www.blogger.com/atom/ns#" term="simulation" /><category scheme="http://www.blogger.com/atom/ns#" term="Python" /><title>A Better Prisoner's Dilemma Simulation?</title><content type="html">In my &lt;a href="http://www.learningtechnicalstuff.com/2011/05/my-generations-project-and-prisoners.html"&gt;previous post&lt;/a&gt;&amp;nbsp;I had started work on a python program named generations to try to replicate the simulations of &lt;a href="http://www.learningtechnicalstuff.com/2011/05/my-generations-project-and-prisoners.html"&gt;Robert Axelrod&lt;/a&gt; in comparing differing Prisoner's Dilemma strategies. At the time I was simulating single organisms ('Critters') with different strategies accumulating food over a number of iterations with the critter with highest food at the end of the iterations judged the winner.&lt;br /&gt;
&lt;br /&gt;
The result of this simple simulation was that two strategies - &lt;i&gt;Grudger&lt;/i&gt; and &lt;i&gt;Tit-for-tat&lt;/i&gt; - were continually vying for the top spot, with Grudger taking the mantle more often than not. In Axelrod's tournament Tit-for-Tat was the unambiguous winner, so I knew my generations script needed some work.&lt;br /&gt;
&lt;br /&gt;
(Read my &lt;a href="http://www.learningtechnicalstuff.com/2011/05/my-generations-project-and-prisoners.html"&gt;previous post&lt;/a&gt; for an introduction to Prisoner's Dilemma, why it interests me and what the various strategies were.)&lt;br /&gt;
&lt;br /&gt;
I've been developing the simulation since. Technically it is a bit of a mess, but when I next refactor I will be focusing on new parts of the model aside from PD and so I wanted to deliver this conclusion to Prisoner's Dilemma now.&lt;br /&gt;
&lt;br /&gt;
In this post I want to talk about how I changed the simulation and what effect these changes had. For the impatient - yes I have managed to tweak the simulation such that Tit-for-tat is the unambigously best strategy. Many critters had to die to make it so...&lt;br /&gt;
&lt;img src="https://spreadsheets0.google.com/a/he3.com.au/spreadsheet/oimg?key=0Ai9fn3aXjsWRdHZjbm5ZZ2lGN21aRWhqall5U2lXcWc&amp;amp;oid=1&amp;amp;zx=bbutf0f0uxtn" /&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;a name='more'&gt;&lt;/a&gt;&lt;b&gt;&amp;nbsp;Go forth and multiply&lt;/b&gt;&lt;br /&gt;
&lt;br /&gt;
In beginning my simulation only ran the same 5 organisms against each other, with each accumulating food indefinitely but at different rates as their strategies proved more or less successful. I specifically wanted to uncover how differing frequencies of strategies influenced the success of themselves or others, so I had to break free of this fixed model.&lt;br /&gt;
&lt;br /&gt;
To this end I introduced reproduction into the simulation, and turned the simulation into one focused on measuring accumulated food to measuring reproductive success. My process for controlling reproduction was simple: When a critter accumulated enough food to pay an arbitrary cost of reproduction, another critter of the same strategy was created, and the cost deducted from the parent.&lt;br /&gt;
&lt;br /&gt;
This change meant that any critter would eventually reproduce - and reproduce multiple times - if it was even marginally successful. More successful strategies would accumulate food faster and thus reproduce faster. Since the offspring could eventually reproduce, this change let to an exploding growth in the populations being tracked.&lt;br /&gt;
&lt;br /&gt;
At the end of the simulation I could now see the success of the strategies: A count of the number of individuals with each strategy and their combined amount of food (which would become an increasingly irrelevant metric).&lt;br /&gt;
&lt;br /&gt;
Adding reproduction and the resulting exploding population immediately pointed out the computational&amp;nbsp;unfeasibility&amp;nbsp;of running the simulation over a large number of iterations in its current form. Each additional critter had to interact with every other which form an exponentially growing computational task. Iteration after iteration, the simulation slowed down.&lt;br /&gt;
&lt;br /&gt;
The results at this stage were unsatisfactory. Over the iteration counts I was prepared to run the simulation for, huge variations were seen from execution to execution. In some executions the Grudgers became the most prolific. In others it was Tit-for-tat. The random, cheater and sucker strategies were competitive on some runs, but not in others.&lt;br /&gt;
&lt;br /&gt;
The source of this variance in outcome was the fortunes of the random strategy and those it interacted with. At this stage the random strategy produced the only non-deterministic part of the simulation. While growth was unrestricted the number of random interactions (interactions involving the Random strategy) was steadily increasing, even as the proportion of those interactions decreased as a part of the total number of interactions.&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;Everyone Eats... Everybody Dies&lt;/b&gt;&lt;br /&gt;
&lt;br /&gt;
To put the breaks on the explosive population growth I next added the concept of death to the simulation. A Critter would die if it ever had an amount of food lower than 0. In theory a Critter could be 'suckered' through a string of interactions into this position, but with the success of the more altruistic strategies, this wasn't very likely. To make success in interactions essential, I added an attritional element to the simulation - That Critters, regardless of their success in interactions - expend a fixed amount of food each iteration.&lt;br /&gt;
&lt;br /&gt;
These additions did not drastically change the outcomes of the simulation. Population growth was slowed, but it was not halted. Within a thousand iterations there were a thousand critters and every iteration more were added than were added the iteration prior. Reaching 1500 iterations took several minutes under these computation conditions.&lt;br /&gt;
&lt;br /&gt;
The success of the random and cheater strategies declined&amp;nbsp;noticeably, however. In the case of the random strategy, statistically assured runs of bad luck thinned their numbers. The cheater strategy could no longer afford to wait between successful cheats, and against the resurgent grudger and tit-for-tat strategies, success for a cheater most often came when encountering the offspring of these two strategies that had never met this individual cheater before.&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;A new form of Random&lt;/b&gt;&lt;br /&gt;
&lt;br /&gt;
With the decline of the Random strategy in the population I wondered if a tweak to its strategy that made cooperation more likely than defection would prosper, or at least be reliably more successful than my initial equal probability implementation. In a simulated world dominated by altruists that held a grudge, defecting once ever two interactions was not performing well.&lt;br /&gt;
&lt;br /&gt;
I changed the random strategy to be more initially flexible and allow the relative weighting of likelihood for cooperation, whilst breeding this weighting true in successive generations. I then added an additional random strategy to the simulation's initial state that was 3 times more likely to cooperate than defect, This strategy was known as Random 3.&lt;br /&gt;
&lt;br /&gt;
To my surprise, the new cooperative random strategy faired no better in the simulation than the completely random strategy. I am still assessing why this might be the case. Grudgers would never forgive a defecting random, but a random strategy less likely to defect would be less likely to continually arouse the punitive defections from the tit-for-tat strategy, which should have been a noticeable advantage.&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;Limited Interactions&lt;/b&gt;&lt;br /&gt;
&lt;br /&gt;
Until this point every iteration provided an opportunity for every critter to interact with every other critter. Universal interaction presented some problems. Apart from contributing to the exponential complexity of the computation, the sheer predictability of each iteration's interactions- varying only from iteration to iteration based on the addition of offspring- meant that altruistic strategies, once entrenched, gained an inevitable advantage that shielded every altruistic critter from risk. For example, if altruistic critters (ie critters with Tit-for-tat or Grudger strategies) made up 75% of the overall population, 75% of all an individual altruistic critter's interactions were certain to end in cooperation, making the consequences of the remaining interactions immaterial to the critter's eventual reproductive success.&lt;br /&gt;
&lt;br /&gt;
Furthermore, as a simulation of non-zero sum social interactions, this universal interaction on every iteration was not realistic. No analagous organism has the capacity to continuously expand its capacity for interaction to all members of a population as the same population undergoes explosive growth.&lt;br /&gt;
&lt;br /&gt;
Because of these issues I implemented a change to the available interactions for each critter each iteration. 5 iterations are chosen at random from amongst the possible combinations. Over multiple iterations and across the entire population, a given strategy will interact with a representative amount of each strategy in the population, but on any given iteration an altruistic critter might interact with entirely non-altruistic strategies. The effect of this change was not noticeable apart from an improvement in speed of the simulation.&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;The Constrained Environment&lt;/b&gt;&lt;br /&gt;
&lt;br /&gt;
At this stage the population the Critter population within my simulation was unendingly growing, albeit at a slower rate, as countless Critters interacted and generated food out of... well nothing. In any real world ecosystem, however, the environment places a constraint on the population size that lives off of it. Apart from the need to achieve a minimum of success in the interaction stakes, my critters had an easy life of interacting and reproducing, and their success, especially for altruistic critters in an altruistically dominated world, did not come at the expense of any other critter.&lt;br /&gt;
&lt;br /&gt;
I wanted to place a population cap on my Critters. This population cap would ideally not sacrifice the successful Critters from current and future generations in order to be maintained. I wanted the population cap to exert a selection pressure on the population that was analagous to the selection pressures in the real world.&lt;br /&gt;
&lt;br /&gt;
To this end I decided not to directly enforce a population cap, but rather, enforce a hard limit to the amount of food available each iteration. With this limit in place, once interactions within an iteration had exhausted the environmental supply of food, no further interactions (and no additions or subtractions of food) were possible. I had no basis to prefer any particular strategy to always appear at the head of the line however, so I was careful to again randomise the interactions which were being considered.&lt;br /&gt;
&lt;br /&gt;
Implemented this constrained environment had a dramatic effect. Firstly, the endless growth was over. For a particular environmental food limit there would be a set population that never varied more than 10 or 20 Critters either side. Suddenly running simulations across much larger numbers of iterations was possible. The effect on the outcome was startling as well: In simulations of 10,000 iterations, cheater and random strategies died out completely early on. The sucker strategy performed well for a time before dwindling and dying out. As before, both guardedly altruistic strategies grudger and tit-for-tat performed well out of the gate, but over the time period the grudger strategy fell by the wayside and leaving tit-for-tat to take the field - &lt;i&gt;just like in the 1980's Axelrod Tournament I was trying to replicate.&lt;/i&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;Enabling Comebacks&lt;/b&gt;&lt;br /&gt;
&lt;br /&gt;
That might have been the end of the story, but I wanted to ensure strategies which were extinct early in the simulation didn't fair better in the late game. For example, would a cheater strategy have more success against a field dominated only by Tit-for-tat critters as opposed to the tit-for-tat/grudger strategy duo seen in the early game? Would a Cooperatively slanted Random strategy do better without the Cheater strategy? Could any strategy upset an entrench tit-for-tat population?&lt;br /&gt;
&lt;br /&gt;
Apart from answering these questions, the question of realism also begged the question. The history of evolution shows that once a trick is learnt, many organisms can retreat to old strategies and ways of life across evolutionary timespans when the environment changes.&lt;br /&gt;
&lt;br /&gt;
I could have simply monitored the environment for strategies that had completely died off and reinjected candidate critters from those strategies at some future time. This seemed overly artificial to me however, so I instead engineered a constant (and yet small - 1%) chance of mutation in any Critter's offspring. When this mutation happened, a Critter with any strategy could emerge. This ensured that no matter which strategy or strategies were in pre-eminence, a 'background noise' of alternative strategies were always waiting in the background to take advantage of a beneficial change in the environment. &amp;nbsp;(An environment is beneficial or not is solely dependent - in this simulation at least - on which other strategies are present in the environment).&lt;br /&gt;
&lt;br /&gt;
There was no amazing change in outcome with this facility for mutation in place. The 'loser strategies' (the randoms, cheaters and later, suckers and grudgers) would invariable go extinct, reappear in small numbers, perhaps become extinct again before re-appearing.&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;Try it yourself&lt;/b&gt;&lt;br /&gt;
&lt;br /&gt;
If I have been vague on providing specific numbers in this description (such as the amount of food consumed per iteration, the exact payoffs for cheating, defection and cooperation, etc) it has been because I have been acutely aware of how arbitrary some of my costs and allowances have been. Gross changes these arbitrary numbers will change the economics of strategy success.&lt;br /&gt;
&lt;br /&gt;
The way these economic constants affect the simulations is interesting and worthy of experimentation.&lt;br /&gt;
&lt;br /&gt;
&lt;i&gt;Question: &lt;/i&gt;If the payoff for cheating is doubled, how what effect does this have on the population?&lt;br /&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="https://spreadsheets.google.com/a/he3.com.au/spreadsheet/oimg?key=0Ai9fn3aXjsWRdG5hX2lVTjloZFJ6YTlSRVVxYzRfWHc&amp;amp;oid=1&amp;amp;zx=4pqo1g784fvm" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"&gt;&lt;img border="0" height="197" src="https://spreadsheets.google.com/a/he3.com.au/spreadsheet/oimg?key=0Ai9fn3aXjsWRdG5hX2lVTjloZFJ6YTlSRVVxYzRfWHc&amp;amp;oid=1&amp;amp;zx=4pqo1g784fvm" width="320" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;
&lt;i&gt;Answer:&lt;/i&gt; Cheaters lead early before the inevitable population crash. Grudgers rise highest out of the critter carnage.&lt;br /&gt;
&lt;br /&gt;
&lt;i&gt;Question: &lt;/i&gt;Does an environment with a higher food-cap, supporting a higher population, have more room for alternative strategies on the fringes?&lt;br /&gt;
&lt;br /&gt;
&lt;i&gt;Answer:&lt;/i&gt; Only slightly&lt;br /&gt;
&lt;br /&gt;
If you want to find out for yourself, download my &lt;a href="https://github.com/bendavieshe3/generations"&gt;generations python script&lt;/a&gt; (use &lt;a href="https://github.com/bendavieshe3/generations/tree/v1.5-PD"&gt;tag v1.5-PD&lt;/a&gt; for this version - as I mentioned in my previous blog generations will not track solely the Prisoner's Dilemma scenario).&lt;br /&gt;
&lt;br /&gt;
The script should be easy to use - run the script without arguments to use default values&lt;br /&gt;
&lt;br /&gt;
&lt;ul&gt;&lt;li&gt;The -i or --iterations option lets you specify the number of iterations run&lt;/li&gt;
&lt;li&gt;The -t or --track option lets you specify an arbitrary critter to track (the initial critter names are c1, s1, r1, r2, t1 and g1 after the strategies. Their offspring are c1_1, c1_2 and c1_1_1, for example, named after their lineage and their birth order from their parent).&amp;nbsp;&lt;/li&gt;
&lt;li&gt;The -r or --reporting options lets you specify the reporting interval&lt;/li&gt;
&lt;li&gt;The -f or --filename option lets you specify a filepath to save a tab-separated datafile to for analysis in spreadsheet and charting programs.&lt;/li&gt;
&lt;li&gt;Use -h or --help to get usage information.&lt;/li&gt;
&lt;/ul&gt;&lt;div&gt;For example:&lt;/div&gt;&lt;code&gt;&lt;br /&gt;
Mimas:src bendavies$ ./generations.py -i 10000 -f cheatpayoff.tsv -r 50&lt;br /&gt;
&lt;/code&gt;&lt;br /&gt;
&lt;br /&gt;
Runs the simulation for 10000 iterations, reports every 50 iterations and outputs the population data to cheatpayoff.tsv&lt;br /&gt;
&lt;br /&gt;
I wouldn't pay too much attention to the technical implementation, since the code is somewhat between plugin, event and direct manipulation architectures at the moment.&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;What is next?&lt;/b&gt;&lt;br /&gt;
&lt;br /&gt;
This exercise was interesting to me since it allowed me to observe emergent population trends based on individual strategies. More-over, I am keen to leave the confines of the prisoner's dilemma simulation and try simulation of other behavioural strategies under evolutionary processes. Some answers I wouldn't mind pursuing would be:&lt;br /&gt;
&lt;br /&gt;
&lt;ul&gt;&lt;li&gt;Can I evolve cooperative strategies from scratch against a selfish population?&lt;/li&gt;
&lt;li&gt;What factors influence parental investment?&lt;/li&gt;
&lt;li&gt;How might embryological complexity weigh in reproduction strategies?&lt;/li&gt;
&lt;li&gt;Can I define a critter genome that is flexible enough to 'mutate slightly', light enough to be computationally feasible and yet expressive enough to allow interesting emergent behaviours and qualities to emerge?&lt;/li&gt;
&lt;li&gt;How would critters move, if given the choice, in an environment whose food resources did not reset every iteration, but rather depleted. How would fragmentation of population affect interaction strategies?&lt;/li&gt;
&lt;li&gt;Would it be possible to evolve critters from a common base to two or more very different lifestyles?&lt;/li&gt;
&lt;/ul&gt;&lt;div&gt;All of this will take time, and I need to take a break to do some work on &lt;a href="http://www.mywebbrain.com/"&gt;My Web Brain&lt;/a&gt; and &lt;a href="http://code.google.com/p/he3-appengine-lib/"&gt;he3-appengine-lib&lt;/a&gt;. Still, I'm tempted to simply dive right in....&lt;/div&gt;&lt;div&gt;&lt;br /&gt;
&lt;/div&gt;&lt;div&gt;If you have any comments on this above exercise please let me know. This has been an absurdly long blog post, but I wanted to share my experience and take the opportunity to examine how this experiment played out.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;
&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9174832840890796616-6209693899299268001?l=www.learningtechnicalstuff.com' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/LearningTechnicalStuff/~4/3fpf7WBYOAg" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://www.learningtechnicalstuff.com/feeds/6209693899299268001/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.learningtechnicalstuff.com/2011/06/better-prisoners-dilemma-simulation.html#comment-form" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/9174832840890796616/posts/default/6209693899299268001?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/9174832840890796616/posts/default/6209693899299268001?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/LearningTechnicalStuff/~3/3fpf7WBYOAg/better-prisoners-dilemma-simulation.html" title="A Better Prisoner's Dilemma Simulation?" /><author><name>Ben Davies</name><uri>http://www.blogger.com/profile/15225977828752157616</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="29" height="32" src="http://4.bp.blogspot.com/_qLhNkiMU1-k/Sh_l4FTCGkI/AAAAAAAAAEY/ABoYTtyFXEA/S220/IMG_2414.jpg" /></author><thr:total>0</thr:total><feedburner:origLink>http://www.learningtechnicalstuff.com/2011/06/better-prisoners-dilemma-simulation.html</feedburner:origLink></entry><entry gd:etag="W/&quot;DkcEQnY-fSp7ImA9WhZVEUk.&quot;"><id>tag:blogger.com,1999:blog-9174832840890796616.post-8925728638263388069</id><published>2011-05-23T02:46:00.000-07:00</published><updated>2011-05-23T02:46:43.855-07:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-05-23T02:46:43.855-07:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="simulation" /><category scheme="http://www.blogger.com/atom/ns#" term="Python" /><title>My Generations Project and Prisoner's Dilemma</title><content type="html">Apologies for the lack of recent posts. I have been 'learning technical stuff' mostly at work and mostly not in a form that can be easily shared. I have recently found the time to start a new project I call Generations - a python program I am developing to simulate some aspects of natural economy and natural selection.&lt;br /&gt;
&lt;br /&gt;
Recently I re-read Richard Dawkins'&amp;nbsp;&lt;a href="http://www.amazon.com/Selfish-Gene-Anniversary-Introduction/dp/0199291152?ie=UTF8&amp;amp;tag=leartechstuf-20&amp;amp;link_code=btl&amp;amp;camp=213689&amp;amp;creative=392969" target="_blank"&gt;The Selfish Gene&lt;/a&gt;&lt;img alt="" border="0" height="1" src="http://www.assoc-amazon.com/e/ir?t=leartechstuf-20&amp;amp;l=btl&amp;amp;camp=213689&amp;amp;creative=392969&amp;amp;o=1&amp;amp;a=0199291152" style="border: none !important; margin: 0px !important; padding: 0px !important;" width="1" /&gt; and Robert Wright's&amp;nbsp;&lt;a href="http://www.amazon.com/Moral-Animal-Science-Evolutionary-Psychology/dp/0679763996?ie=UTF8&amp;amp;tag=leartechstuf-20&amp;amp;link_code=btl&amp;amp;camp=213689&amp;amp;creative=392969" target="_blank"&gt;The Moral Animal&lt;/a&gt;&lt;img alt="" border="0" height="1" src="http://www.assoc-amazon.com/e/ir?t=leartechstuf-20&amp;amp;l=btl&amp;amp;camp=213689&amp;amp;creative=392969&amp;amp;o=1&amp;amp;a=0679763996" style="border: none !important; margin: 0px !important; padding: 0px !important;" width="1" /&gt;&amp;nbsp;and was fascinated by the idea that computer simulations could tell us useful information about effective behavioral strategies, how those strategies prosper and in turn what effect these strategies may have on their own effectiveness and that of other strategies.&lt;br /&gt;
&lt;br /&gt;
Currently this initial version of my program only simulates Prisoner's Dilemma. Wikipedia &lt;a href="http://en.wikipedia.org/wiki/Prisoner's_dilemma"&gt;describes&lt;/a&gt; the Prisoner's Dilemma scenario, a classic game from the field of Game Theory, like this:&lt;br /&gt;
&lt;blockquote&gt;Two suspects are arrested by the police. The police have insufficient evidence for a conviction, and, having separated the prisoners, visit each of them to offer the same deal. If one testifies for the prosecution against the other (defects) and the other remains silent (cooperates), the defector goes free and the silent accomplice receives the full one-year sentence. If both remain silent, both prisoners are sentenced to only one month in jail for a minor charge. If each betrays the other, each receives a three-month sentence. Each prisoner must choose to betray the other or to remain silent. Each one is assured that the other would not know about the betrayal before the end of the investigation. How should the prisoners act?&lt;/blockquote&gt;Prisoner's Dilemma is an example of a non-zero-sum game, meaning that players are not attempting to beat other players, just achieve the best outcome for themselves. That is to say: winning doesn't automatically imply the other player or players have lost. When played repeatedly and scored (ie. 'Iterative Prisoner's Dilemma') the interesting question becomes which strategy, if any, is the most successful against the widest range of counter-strategies. Always cooperate? Always defect? Some mixture of the two?&lt;br /&gt;
&lt;br /&gt;
&lt;a name='more'&gt;&lt;/a&gt;&lt;br /&gt;
The relevance of this game to the field of Darwinism (which both Dawkins and Wright were asserting) is that this situation of deciding to trust (or abuse trust) is a good abstraction of the issues underlying &lt;a href="http://en.wikipedia.org/wiki/Reciprocal_altruism"&gt;reciprocal altruism&lt;/a&gt;, one of the motivators of altruistic (or 'nice') behaviour in the neo-Darwinist worldview. Both Dawkins and Wright discussed a competition held in the 1970s and again in the early 1980's where Robert Alexrod challenged interested parties to design strategies for the game and test them out against the strategies of other players.&lt;br /&gt;
&lt;br /&gt;
I am running my own tournament of sorts within Generations. Instead of the prisoner analogy however, my program simulates 'critters' adopting different strategies and receiving or losing 'food' as a result of the success or failure at the prisoner's dilemma game. I have currently implemented the following strategies:&lt;br /&gt;
&lt;br /&gt;
&lt;ul&gt;&lt;li&gt;Sucker, who always cooperates blindly&lt;/li&gt;
&lt;li&gt;Cheater, who always defects&lt;/li&gt;
&lt;li&gt;Random, who randomly cooperates or defects&lt;/li&gt;
&lt;li&gt;Grudger, who cooperates with anyone who has never defected against them, and&lt;/li&gt;
&lt;li&gt;Tit-For-Tat, who cooperates initially but punishes defections in the previous round by another player with a defection in the current round.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;&lt;div&gt;My ratio of scoring for defection and cooperation is arbitrary and by default the simulation runs over 1500 iterations. The most successful strategies so far are Grudger and Tit-For-Tat, With Grudger always just beating out Tit-for-Tat.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;
&lt;/div&gt;&lt;div&gt;In Alexrod's tournament, Tit-for-Tat was judged the winner. One reason why my simulation might be playing out differently is that I have precisely one critter for each strategy, and whereas once bitten Grudger will never trust random again, Tit-for-Tat will 'trust' Random again after a random cooperation.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;
&lt;/div&gt;&lt;div&gt;The specific economic rewards I have assigned might also be making a difference.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;
&lt;/div&gt;&lt;div&gt;&lt;div&gt;In the real-world, less successful 'critters' would reproduce in proportion with their success, and their hereditary strategies would be passed on their offspring. Critters with bad strategies would shrink as a proportion of the population, altering the dynamics of which strategy is more effective. Simulating this, as well as some other strategies I found on the Wikipedia page, is what I will do next.&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;
&lt;/div&gt;&lt;div&gt;In the meantime, you can get my &lt;a href="https://github.com/bendavieshe3/generations"&gt;Generations program on Github&lt;/a&gt;. Use the v*-PD tags to download the Prisoner's Dilemma only versions - I will be adding decidedly un-Prisoner's Dilemma elements (ie. other ways to simulate critters) in the near future. The current PD tag is &lt;a href="https://github.com/bendavieshe3/generations/commits/v1.0-PD"&gt;v1.0-PD&lt;/a&gt;.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;
&lt;/div&gt;&lt;div&gt;Let me know if you have questions, or comments.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;
&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9174832840890796616-8925728638263388069?l=www.learningtechnicalstuff.com' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/LearningTechnicalStuff/~4/BG5rvHLqI2k" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://www.learningtechnicalstuff.com/feeds/8925728638263388069/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.learningtechnicalstuff.com/2011/05/my-generations-project-and-prisoners.html#comment-form" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/9174832840890796616/posts/default/8925728638263388069?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/9174832840890796616/posts/default/8925728638263388069?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/LearningTechnicalStuff/~3/BG5rvHLqI2k/my-generations-project-and-prisoners.html" title="My Generations Project and Prisoner's Dilemma" /><author><name>Ben Davies</name><uri>http://www.blogger.com/profile/15225977828752157616</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="29" height="32" src="http://4.bp.blogspot.com/_qLhNkiMU1-k/Sh_l4FTCGkI/AAAAAAAAAEY/ABoYTtyFXEA/S220/IMG_2414.jpg" /></author><thr:total>0</thr:total><feedburner:origLink>http://www.learningtechnicalstuff.com/2011/05/my-generations-project-and-prisoners.html</feedburner:origLink></entry><entry gd:etag="W/&quot;CkEERHYzeSp7ImA9Wx9bEU0.&quot;"><id>tag:blogger.com,1999:blog-9174832840890796616.post-2448169466401761125</id><published>2011-02-18T23:18:00.000-08:00</published><updated>2011-02-18T23:30:05.881-08:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-02-18T23:30:05.881-08:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="he3-appengine-lib" /><title>PagedQuery not working on SQLite Google App Engine SDK..?</title><content type="html">A recent interchange with a helpful user gave me a heads up that &lt;a href="http://code.google.com/p/he3-appengine-lib/wiki/PagedQuery"&gt;PagedQuery&lt;/a&gt; - my simple query wrapper for providing paging in Google App Engine - might not work when testing locally using the SDK with the &lt;a href="http://code.google.com/appengine/docs/python/tools/devserver.html#Switching_to_SQLite_for_Your_Local_Datastore"&gt;optional SQLite backend&lt;/a&gt;&amp;nbsp;(there are number of &lt;a href="http://code.google.com/p/googleappengine/issues/list?can=2&amp;amp;q=sqlite&amp;amp;colspec=ID+Type+Component+Status+Stars+Summary+Language+Priority+Owner+Log&amp;amp;cells=tiles"&gt;open issues about SQLite&lt;/a&gt; on the &lt;a href="http://code.google.com/p/googleappengine/"&gt;SDK project page&lt;/a&gt;).&lt;br /&gt;
&lt;br /&gt;
I haven't used SQLite backend personally, so for the moment I can't confirm this. From my discussion with the user it seems the SQLite backend does not implement cursors properly. I'm slowly reviving my &lt;a href="http://code.google.com/p/he3-appengine-lib/"&gt;he3-appengine-lib&lt;/a&gt; development environment so hopefully I will be in a position to confirm this soon.&lt;br /&gt;
&lt;br /&gt;
(Thanks Milo for bringing this to my attention!)&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9174832840890796616-2448169466401761125?l=www.learningtechnicalstuff.com' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/LearningTechnicalStuff/~4/adsfNYeUiPU" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://www.learningtechnicalstuff.com/feeds/2448169466401761125/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.learningtechnicalstuff.com/2011/02/pagedquery-not-working-on-sqlite-google.html#comment-form" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/9174832840890796616/posts/default/2448169466401761125?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/9174832840890796616/posts/default/2448169466401761125?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/LearningTechnicalStuff/~3/adsfNYeUiPU/pagedquery-not-working-on-sqlite-google.html" title="PagedQuery not working on SQLite Google App Engine SDK..?" /><author><name>Ben Davies</name><uri>http://www.blogger.com/profile/15225977828752157616</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="29" height="32" src="http://4.bp.blogspot.com/_qLhNkiMU1-k/Sh_l4FTCGkI/AAAAAAAAAEY/ABoYTtyFXEA/S220/IMG_2414.jpg" /></author><thr:total>0</thr:total><feedburner:origLink>http://www.learningtechnicalstuff.com/2011/02/pagedquery-not-working-on-sqlite-google.html</feedburner:origLink></entry><entry gd:etag="W/&quot;C0UBRH09fCp7ImA9Wx9UEEU.&quot;"><id>tag:blogger.com,1999:blog-9174832840890796616.post-1731365691023161147</id><published>2011-02-07T04:20:00.000-08:00</published><updated>2011-02-07T04:20:55.364-08:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-02-07T04:20:55.364-08:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="Blogger" /><title>Naked Domains on Blogger</title><content type="html">Google sometimes isn't&amp;nbsp;great at updating its documentation. Or perhaps they need to improve their search. If you search for information about how to use a naked domain on &lt;a href="http://www.blogger.com/"&gt;Blogger&lt;/a&gt;, you will get the impression from results that this isn't possible.&lt;br /&gt;
&lt;br /&gt;
The &lt;a href="http://www.google.com/support/blogger/bin/answer.py?hl=en&amp;amp;answer=139485"&gt;highest search result&lt;/a&gt; I found from Blogger seems to say the same thing:&lt;br /&gt;
&lt;blockquote&gt;To simplify custom domain configuration on your blog, Blogger only accepts non-naked domains in our sign-up process. If you try to enter a naked domain in the advanced settings of the Settings | Publishing tab, you will run into an error stating that Blogs may not be hosted at naked domains.&lt;br /&gt;
&lt;br /&gt;
To fix this, simply enter the non-naked version of your domain and save your settings.&lt;/blockquote&gt;But you can use a naked domain! (sort of). No hosting, redirect pages or .htaccess files required. You can achieve the same result by configuring some A records on your naked domain to redirect to Google's server.&lt;br /&gt;
&lt;br /&gt;
I knew I saw this somewhere while I was setting up &lt;a href="http://fernyblog.com/"&gt;Ferny Blog&lt;/a&gt; but it has taken me far too long to find the information in Google's documentation.&lt;br /&gt;
&lt;br /&gt;
But found it I did - Look at the second half of &lt;a href="http://www.google.com/support/blogger/bin/answer.py?hl=en&amp;amp;answer=55373"&gt;this page&lt;/a&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9174832840890796616-1731365691023161147?l=www.learningtechnicalstuff.com' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/LearningTechnicalStuff/~4/20vO0X4qOaA" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://www.learningtechnicalstuff.com/feeds/1731365691023161147/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.learningtechnicalstuff.com/2011/02/naked-domains-on-blogger.html#comment-form" title="3 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/9174832840890796616/posts/default/1731365691023161147?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/9174832840890796616/posts/default/1731365691023161147?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/LearningTechnicalStuff/~3/20vO0X4qOaA/naked-domains-on-blogger.html" title="Naked Domains on Blogger" /><author><name>Ben Davies</name><uri>http://www.blogger.com/profile/15225977828752157616</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="29" height="32" src="http://4.bp.blogspot.com/_qLhNkiMU1-k/Sh_l4FTCGkI/AAAAAAAAAEY/ABoYTtyFXEA/S220/IMG_2414.jpg" /></author><thr:total>3</thr:total><feedburner:origLink>http://www.learningtechnicalstuff.com/2011/02/naked-domains-on-blogger.html</feedburner:origLink></entry><entry gd:etag="W/&quot;DE4CSX44cSp7ImA9Wx9WE0k.&quot;"><id>tag:blogger.com,1999:blog-9174832840890796616.post-5111229920669598712</id><published>2011-01-17T23:43:00.000-08:00</published><updated>2011-01-18T02:36:08.039-08:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-01-18T02:36:08.039-08:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="mockups" /><category scheme="http://www.blogger.com/atom/ns#" term="Eclipse" /><category scheme="http://www.blogger.com/atom/ns#" term="wireframes" /><title>Eclipse-Based Wireframe and Mockup Designer: WireframeSketcher</title><content type="html">If you are in need of a capable wireframe and mockup designer for &lt;a href="http://www.eclipse.org/"&gt;Eclipse&lt;/a&gt;, this post will interest you. I was contacted by Peter Severin from &lt;a href="http://wireframesketcher.com/"&gt;WireframeSketcher&lt;/a&gt; last month. Peter asked if I was interested in trying out his Eclipse-based wireframe and mockup designer. He also provided a free license key. Thanks Peter!&lt;br /&gt;
&lt;br /&gt;
In truth, I had been looking around for an easier solution to wireframes and mockups. I had been trying to use &lt;a href="http://www.adobe.com/products/fireworks/"&gt;Adobe Fireworks CS5&lt;/a&gt; in combination with&lt;a href="http://www.lynda.com/tutorial/69793"&gt; a course from Lynda.com&lt;/a&gt; for this purpose but I found the solution and workflow overkill for a web application designer working on his own projects. I need something simpler.&lt;br /&gt;
&lt;br /&gt;
And then there is the issue of platform support. As I have mentioned in previous posts, &amp;nbsp;I develop on Ubuntu and Mac OSX. My Windows license for CS5 would not help me on those platforms, and running additional virtual machines for the purpose simply complicated my workflow and source control.&lt;br /&gt;
&lt;br /&gt;
&lt;a href="http://balsamiq.com/"&gt;Balsamiq &lt;/a&gt;looks like a great tool, and I have heard many good things about it, but it suffers from the same lack of platform support on Ubuntu and is otherwise just another tool to manage and integrate into workflow and source control.&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;WireframeSketcher, on the other hand, is built on Eclipse&lt;/b&gt;. It is available in a standalone application, but really comes into its own as a plugin into your existing Eclipse environment. WireframeSketcher, like Eclipse, is cross-platform, and works just as well on Ubuntu or Mac OSX as it does on Windows.&lt;br /&gt;
&lt;br /&gt;
&lt;a name='more'&gt;&lt;/a&gt;&lt;br /&gt;
&lt;b&gt;Working with wireframes and mockups in your IDE saves time and simplifies workflow&lt;/b&gt;. Without the need to switch applications I found I was using wireframes much more effectively. WireframeSketcher saves designs and mockups as individual .screen files, making them easy to manage within the same project as your source code and using the same source control.&lt;br /&gt;
&lt;br /&gt;
WireframeSketcher is like other full-featured plugins for Eclipse. It provides an editor, several views and a perspective for easy switching to wireframe or mockup design.&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;Within those Eclipse conventions, WireframeSketcher is like many other wireframing solutions&lt;/b&gt;: you drag widgets from a&amp;nbsp;palette&amp;nbsp;onto your canvas. A properties panel allows fine-grained control and configuration. Clickable links can be defined between screens, and boilerplate or common layout can be defined in master screens that is reused across other mockups. These features make WireframeSketcher both easy to use and the equal of other wireframe tools in all the ways that matter to me.&lt;br /&gt;
&lt;br /&gt;
Apart from the benefits from these features and that the integration with Eclipse provides, I will mention three features that surprised and impressed me: Firstly, not only can wireframes be exported to PDF, PNG and to the clipboard, this &lt;b&gt;export can be done as a batch operation&lt;/b&gt;. Secondly, Wireframe styles and controls text and data tables using a&amp;nbsp;&lt;b&gt;powerful&amp;nbsp;text markup language that provides a lot of flexibility&lt;/b&gt;. Thirdly, the plugin has been &lt;b&gt;remarkably solid and well behaved&lt;/b&gt;, unlike many other Eclipses plugins I have tried. It feels polished and refined.&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;Which is not to say that WireframeSketcher could not be improved&lt;/b&gt;. The widget palette could be categorized and easier to navigate. Using the tool is quick, but I think the time spent trying to find a particular widget in the palette could be reduced. WireframeSketcher does not have as many out-of-the-box widgets in the palette as some other tools, although this has not caused me an issue yet.&amp;nbsp;Lastly, the assets functionality, which complements the standard palette with images and master screens of your own, could be improved. Assets require a specifically named and located folder in your project structure. Personally I would prefer a customizable location &amp;nbsp;for these assets to suit the variety of ways Eclipse may be used. Reuse of assets between projects is not addressed.&lt;br /&gt;
&lt;br /&gt;
I should be clear, however, that &lt;b&gt;these criticisms are relatively minor&lt;/b&gt; and have not prevented me from using WireframeSketcher effectively. &lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;&lt;a href="http://wireframesketcher.com/"&gt;WireframeSketcher&lt;/a&gt; is an excellent mockup and wireframe tool&lt;/b&gt;, and I definitely recommend &lt;a href="http://wireframesketcher.com/install.html"&gt;giving it a try&lt;/a&gt;, especially if you live inside the Eclipse IDE. For those of us that need to both design and develop applications the integrated nature of the plugin both enhances your productivity and simplifies workflow. In case I have not been clear, &lt;i&gt;&lt;b&gt;I really like WireframeSketcher!&lt;/b&gt;&lt;/i&gt;&lt;br /&gt;
&lt;br /&gt;
I would like to thank Peter again for introducing me to WireframeSketcher and providing a free license key, and I hope this blog post plays a part in providing more exposure to his excellent tool.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9174832840890796616-5111229920669598712?l=www.learningtechnicalstuff.com' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/LearningTechnicalStuff/~4/8oLcmCd1aBc" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://www.learningtechnicalstuff.com/feeds/5111229920669598712/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.learningtechnicalstuff.com/2011/01/eclipse-based-wireframe-and-mockup.html#comment-form" title="1 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/9174832840890796616/posts/default/5111229920669598712?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/9174832840890796616/posts/default/5111229920669598712?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/LearningTechnicalStuff/~3/8oLcmCd1aBc/eclipse-based-wireframe-and-mockup.html" title="Eclipse-Based Wireframe and Mockup Designer: WireframeSketcher" /><author><name>Ben Davies</name><uri>http://www.blogger.com/profile/15225977828752157616</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="29" height="32" src="http://4.bp.blogspot.com/_qLhNkiMU1-k/Sh_l4FTCGkI/AAAAAAAAAEY/ABoYTtyFXEA/S220/IMG_2414.jpg" /></author><thr:total>1</thr:total><feedburner:origLink>http://www.learningtechnicalstuff.com/2011/01/eclipse-based-wireframe-and-mockup.html</feedburner:origLink></entry><entry gd:etag="W/&quot;D0IDSXg4eSp7ImA9Wx9XEUw.&quot;"><id>tag:blogger.com,1999:blog-9174832840890796616.post-4215185849553755547</id><published>2011-01-03T20:11:00.000-08:00</published><updated>2011-01-03T20:32:58.631-08:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-01-03T20:32:58.631-08:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="Speed" /><category scheme="http://www.blogger.com/atom/ns#" term="Google App Engine" /><category scheme="http://www.blogger.com/atom/ns#" term="Apache Ant" /><title>Static Resources and Cache-Busting on Google App Engine Python</title><content type="html">Hello and happy new year!&lt;br /&gt;
&lt;br /&gt;
In a couple of &lt;a href="http://www.learningtechnicalstuff.com/2010/12/using-closure-javascript-optimization.html"&gt;previous&lt;/a&gt; &lt;a href="http://www.learningtechnicalstuff.com/2010/12/automating-closure-compiler-with-ant.html"&gt;posts&lt;/a&gt; I discussed using Google Closure JavaScript Compiler in your build process to minimise the size of your JavaScript resources. Arguably a bigger win for the end user experience is to ensure the caching of all of your website static resources (JavaScript, stylesheets and images) is properly optimised. In this post I want to share how I think I have achieved this &lt;a href="http://code.google.com/appengine/"&gt;Google App Engine&lt;/a&gt; (Python). &lt;br /&gt;
&lt;br /&gt;
Ideally, we would like the end user's browser to use its cache for every static resource, on every request, until that resource changes because we have deployed a new application version. Adding the (relatively new) &lt;a href="http://code.google.com/appengine/docs/python/config/appconfig.html#Static_File_Handlers"&gt;&lt;code&gt;default_expiration&lt;/code&gt;&lt;/a&gt; and &lt;a href="http://code.google.com/appengine/docs/python/config/appconfig.html#Static_Directory_Handlers"&gt;&lt;code&gt;expiration&lt;/code&gt;&lt;/a&gt; directives to your app.yaml file will achieve the first part of this requirement:&lt;br /&gt;
&lt;br /&gt;
&lt;pre style="background: #eee; border: 1px solid #090; font-size: smaller; overflow: auto; padding: 5px;"&gt;&lt;code&gt;- url: /images
  static_dir: images
  expiration: "99d"
&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;
Behind the scenes, these directives add additional headers to the HTTP response, letting the browser know that they can safely serve these files from cache on subsequent requests.&lt;br /&gt;
&lt;br /&gt;
But how do we tell the end user browser when our static resources have changed?&lt;br /&gt;
&lt;br /&gt;
&lt;a name='more'&gt;&lt;/a&gt;&lt;br /&gt;
The solution is called cache-busting, and there are a number of similiar approaches. These involve changing the request URI for the static resources. This could mean appending a version identifier to the individual JS, CSS and image filenames. When the browser sees the request for a nominally different file it has no reason to pull the cached copy, so it requests the file again, with the specified caching headers in place again to prevent it being requested again:&lt;br /&gt;
&lt;code style="font-size: smaller;"&gt;&lt;br /&gt;
First Request&lt;br /&gt;
URI: yourapp.blogspot.com\images\banner_v1.jpg (requested)&lt;br /&gt;
&lt;br /&gt;
Second Request&lt;br /&gt;
URI: yourapp.appspot.com\images\banner_v1.jpg (not requested - cached)&lt;br /&gt;
&lt;br /&gt;
Third Request&lt;br /&gt;
URI: yourapp.appspot.com\images\banner_v2.jpg (updated file - re-requested)&lt;br /&gt;
&lt;br /&gt;
Fourth Request&lt;br /&gt;
URI: yourapp.appspot.com\images\banner_v2.jpg (not requested - cached)&lt;br /&gt;
&lt;/code&gt;&lt;br /&gt;
&lt;br /&gt;
This technique works, but how you consistently achieve it without introducing errors, confusing source control with trivial changes or simply absorbing too much development time is worth a discussion. Making manual changes to resource file names, the app.yaml static resource directives and the HTML for every application deployment is not very practical. &lt;br /&gt;
&lt;br /&gt;
My approach to removing the development pain associated with cache busting works as follows: Instead of mangling the actual filename of the resource, I change the URI path. This means filenames themselves do not need to change. I also setup static file directives in the app.yaml such that the intermediate path variance (due to changing version) is ignored:&lt;br /&gt;
&lt;br /&gt;
&lt;code&gt;&lt;/code&gt;&lt;br /&gt;
&lt;pre style="background: #eee; border: 1px solid #090; font-size: smaller; overflow: auto; padding: 5px;"&gt;&lt;code&gt;- url: /images/.*/(.*)
  static_files: static/img/\1
  upload: static/img/(.*)
&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;
&lt;br /&gt;
Using a directives such as this, the position or naming of the actual static resource in the source code never changes. For example, the following URI's all serve the same static file:&lt;br /&gt;
&lt;br /&gt;
&lt;code&gt;yourapp.appspot.com/images/v1/banner.jpg&lt;/code&gt; » &lt;code&gt;/static/img/banner.jpg&lt;/code&gt;&lt;br /&gt;
&lt;code&gt;yourapp.appspot.com/images/v2/banner.jpg&lt;/code&gt; » &lt;code&gt;/static/img/banner.jpg&lt;/code&gt;&lt;br /&gt;
&lt;br /&gt;
Within the HTML (or any other resource specifying a URI) the source code contains a known token: &lt;br /&gt;
&lt;br /&gt;
&lt;code&gt;img src="/images/&lt;em&gt;xx-replace-with-version-id-zz&lt;/em&gt;/banner.jpg"&lt;/code&gt;&lt;br /&gt;
&lt;br /&gt;
My build script, written in &lt;a href="http://ant.apache.org/"&gt;Apache Ant&lt;/a&gt;, replaces the known token in the prepared build files with the version identifier. The original source files (whose known token works fine in development) are not updated:&lt;br /&gt;
&lt;br /&gt;
&lt;pre style="background: #eee; border: 1px solid #090; font-size: smaller; overflow: auto; padding: 5px;"&gt;&lt;code&gt;&amp;lt;copy overwrite="true" todir="${path.build.src}"&amp;gt;
    &amp;lt;fileset dir="${path.assets.src}"&amp;gt;
      &amp;lt;include name="**/*.html" /&amp;gt;
      &amp;lt;include name="**/*.css" /&amp;gt;
    &amp;lt;/fileset&amp;gt;
 
    &amp;lt;filterset begintoken="xx-" endtoken="-zz"&amp;gt;
      &amp;lt;filter token="replace-with-version-id" value="${generated.id.timestamp}" /&amp;gt;
    &amp;lt;/filterset&amp;gt;
&amp;lt;/copy&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;
&lt;br /&gt;
The build script goes on to deploy the application either to its online test or production versions and then uses the same version identifier to tag newly deployed source code in my source control.  &lt;br /&gt;
&lt;br /&gt;
I know some developers prefer using a dynamically generated version identifier (using &lt;code&gt;os.environ['CURRENT_VERSION_ID']&lt;/code&gt;) within their HTML or other URI carrying source. Since I already use a &lt;a href="http://www.learningtechnicalstuff.com/2010/12/automating-closure-compiler-with-ant.html"&gt;build script for JS compilation&lt;/a&gt; and Python linting, my approach of replacing a known token makes more sense to me - It removes complexity from my code and allows a full correspondence with the version identifier between the deployed version and the source control records.&lt;br /&gt;
&lt;br /&gt;
I'd be interested in hearing if someone has an idea for improving this process or thinks another approach has distinct advantages I have not considered. If you have questions, leave them here as well.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9174832840890796616-4215185849553755547?l=www.learningtechnicalstuff.com' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/LearningTechnicalStuff/~4/a5vWE__dePE" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://www.learningtechnicalstuff.com/feeds/4215185849553755547/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.learningtechnicalstuff.com/2011/01/static-resources-and-cache-busting-on.html#comment-form" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/9174832840890796616/posts/default/4215185849553755547?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/9174832840890796616/posts/default/4215185849553755547?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/LearningTechnicalStuff/~3/a5vWE__dePE/static-resources-and-cache-busting-on.html" title="Static Resources and Cache-Busting on Google App Engine Python" /><author><name>Ben Davies</name><uri>http://www.blogger.com/profile/15225977828752157616</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="29" height="32" src="http://4.bp.blogspot.com/_qLhNkiMU1-k/Sh_l4FTCGkI/AAAAAAAAAEY/ABoYTtyFXEA/S220/IMG_2414.jpg" /></author><thr:total>0</thr:total><feedburner:origLink>http://www.learningtechnicalstuff.com/2011/01/static-resources-and-cache-busting-on.html</feedburner:origLink></entry><entry gd:etag="W/&quot;DUIARXY8eSp7ImA9Wx9REUQ.&quot;"><id>tag:blogger.com,1999:blog-9174832840890796616.post-1293217244367414179</id><published>2010-12-12T15:45:00.000-08:00</published><updated>2010-12-12T15:45:44.871-08:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2010-12-12T15:45:44.871-08:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="Closure Compiler" /><category scheme="http://www.blogger.com/atom/ns#" term="Apache Ant" /><title>Automating Closure Compiler with Ant</title><content type="html">In a &lt;a href="http://www.learningtechnicalstuff.com/2010/12/using-closure-javascript-optimization.html"&gt;previous post&lt;/a&gt; I talked about how to use &lt;a href="http://code.google.com/closure/compiler/"&gt;Google's Closure Compiler&lt;/a&gt; to optimize and minify JavaScript files.&amp;nbsp;Now I have shifted to a dual Ubuntu and Mac OS X development environment, I am determined to invest in a strong automatic build process. This is just as well, since compiled JavaScript is impossible to maintain or develop. Compiling JavaScript needs to be performed automatically and transparently.&lt;br /&gt;
&lt;br /&gt;
I am building out my build scripts using &lt;a href="http://ant.apache.org/"&gt;Apache Ant&lt;/a&gt;. Ant is cross-platform compatible and is well supported in the Eclipse-based Aptana and Pydev development environments. Automating the JavaScript compilation using Ant proved a bit troublesome.&lt;br /&gt;
&lt;br /&gt;
In Ant you use the &lt;a href="http://ant.apache.org/manual/Tasks/apply.html"&gt;Apply Task&lt;/a&gt; to run executable programs. Running Closure Compiler using the Apply task for each JavaScript file was the first hurdle, but Apply has obvious support for passing a FileSet to determine what those files would be. The bigger challenge was working out how to specify the input and output files using redirection.&lt;br /&gt;
&lt;br /&gt;
My task was made a lot easier when I found a useful example of using Ant with JSMin as the last example on the &lt;a href="http://ant.apache.org/manual/Tasks/apply.html"&gt;documentation page&lt;/a&gt;, which is invoked in similar fashion to Closure Compiler. Modifying the example I came up with this Ant Target:&lt;br /&gt;
&lt;pre style="background-color:#EEE;font-size:smaller;overflow:scroll"&gt;&lt;code&gt;
  &amp;lt;target name=&amp;quot;-compilejs&amp;quot; depends=&amp;quot;-copybuild&amp;quot;&lt;br/&gt;   description=&amp;quot;(runs the closure js compiler against all javascript files to replace the files in the build folder&amp;quot;&amp;gt;&lt;br/&gt; &lt;br/&gt;   &amp;lt;!-- modeled off example found at http://ant.apache.org/manual/Tasks/apply.html --&amp;gt;&lt;br/&gt;   &amp;lt;apply executable=&amp;quot;java&amp;quot; parallel=&amp;quot;false&amp;quot; failonerror=&amp;quot;true&amp;quot; addsourcefile=&amp;quot;false&amp;quot;&amp;gt;&lt;br/&gt;     &amp;lt;arg value=&amp;quot;-jar&amp;quot; /&amp;gt;&lt;br/&gt;     &amp;lt;arg value=&amp;quot;${path.assets.bin.closurecompiler}&amp;quot; /&amp;gt;&lt;br/&gt;        &amp;lt;arg line=&amp;quot;--warning_level QUIET&amp;quot; /&amp;gt; &lt;br/&gt;    &amp;lt;arg line=&amp;quot;--compilation_level SIMPLE_OPTIMIZATIONS&amp;quot; /&amp;gt;&lt;br/&gt;     &amp;lt;fileset dir=&amp;quot;${path.assets.src}&amp;quot; includes=&amp;quot;**/*.js&amp;quot; /&amp;gt;&lt;br/&gt;     &amp;lt;redirector&amp;gt;&lt;br/&gt;        &amp;lt;inputmapper type=&amp;quot;glob&amp;quot; from=&amp;quot;*.js&amp;quot; to=&amp;quot;${path.assets.src}/*.js&amp;quot; /&amp;gt;&lt;br/&gt;        &amp;lt;outputmapper type=&amp;quot;glob&amp;quot; from=&amp;quot;*.js&amp;quot; to=&amp;quot;${path.build.src}/*.js&amp;quot; /&amp;gt;&lt;br/&gt;     &amp;lt;/redirector&amp;gt;&lt;br/&gt;    &amp;lt;/apply&amp;gt; &lt;br/&gt;  &amp;lt;/target&amp;gt; 
&lt;/code&gt;&lt;/pre&gt;This isn't perfect. This approach always compiles all of the JavaScript files, even when they have not been altered. and JavaScript compilation with Closure Compiler is currently the longest running activity of my build. I will be trying to tweak Apply invocation to get this fixed.&lt;br /&gt;
&lt;br /&gt;
The other problem is the suppression of warnings. Warnings appear inline in the output JavaScript, which breaks the JavaScript file but does not fail the build, which is what I would prefer. I am wondering if I redirect the error output and check its contents and manually fail the build whether that will catch the problem, at which point I can remove the (dangerous) suppression of warnings.&lt;br /&gt;
&lt;br /&gt;
I will let you know when I surmount these problems, but if you have any thoughts let me know. I will also go into more detail about the rest of my build process - specifically as it applies to Google App Engine, which is probably the more unique aspect, and therefore, most useful.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9174832840890796616-1293217244367414179?l=www.learningtechnicalstuff.com' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/LearningTechnicalStuff/~4/uF5sKEFIN8E" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://www.learningtechnicalstuff.com/feeds/1293217244367414179/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.learningtechnicalstuff.com/2010/12/automating-closure-compiler-with-ant.html#comment-form" title="1 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/9174832840890796616/posts/default/1293217244367414179?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/9174832840890796616/posts/default/1293217244367414179?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/LearningTechnicalStuff/~3/uF5sKEFIN8E/automating-closure-compiler-with-ant.html" title="Automating Closure Compiler with Ant" /><author><name>Ben Davies</name><uri>http://www.blogger.com/profile/15225977828752157616</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="29" height="32" src="http://4.bp.blogspot.com/_qLhNkiMU1-k/Sh_l4FTCGkI/AAAAAAAAAEY/ABoYTtyFXEA/S220/IMG_2414.jpg" /></author><thr:total>1</thr:total><feedburner:origLink>http://www.learningtechnicalstuff.com/2010/12/automating-closure-compiler-with-ant.html</feedburner:origLink></entry><entry gd:etag="W/&quot;CUQDRXo4eyp7ImA9Wx9SF08.&quot;"><id>tag:blogger.com,1999:blog-9174832840890796616.post-1891029204971953221</id><published>2010-12-07T04:02:00.000-08:00</published><updated>2010-12-07T04:02:54.433-08:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2010-12-07T04:02:54.433-08:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="JavaScript" /><category scheme="http://www.blogger.com/atom/ns#" term="Closure Compiler" /><title>Using Closure JavaScript Optimization and Minification</title><content type="html">Recently I attended &lt;a href="http://www.cfobjective.com.au/"&gt;cf.Objective(ANZ)&lt;/a&gt;, a Melbourne based conference for Enterprise ColdFusion developers. A session which stuck with me was titled 'Speed Matters' and it was presented by &lt;a href="http://twitter.com/#!/markstanto"&gt;Mark Stanton&lt;/a&gt;. Mark presented a compelling case for&amp;nbsp;optimizing&amp;nbsp;a web site's HTTP usage for a faster and more efficient experience for the user.&lt;br /&gt;
&lt;br /&gt;
Now that I have rebooted my development environment in Ubuntu and Mac&amp;nbsp;OS X&amp;nbsp;I am looking forward to applying some HTTP&amp;nbsp;optimization&amp;nbsp;to my own site,&amp;nbsp;&lt;a href="http://www.mywebbrain.com/"&gt;My Web Brain&lt;/a&gt;. There are a lot of different areas I can (and will) target, but first off is&amp;nbsp;JavaScript&amp;nbsp;minification and&amp;nbsp;optimization.&lt;br /&gt;
&lt;br /&gt;
To get some experience with a new tool which did more than simply strip out comments and whitespace, I decided to try Google's Closure Compiler. &lt;a href="http://code.google.com/closure/compiler/"&gt;Closure&amp;nbsp;JavaScript&amp;nbsp;Compiler&lt;/a&gt; is distributed as a simple jar file and works with standard input and output streams:&lt;br /&gt;
&lt;br /&gt;
&lt;code&gt;$java -jar compiler.jar &amp;lt; in.js &amp;gt; out.js&lt;/code&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;a name='more'&gt;&lt;/a&gt;The compiler can also accept multiple&amp;nbsp;JavaScript&amp;nbsp;filenames from&amp;nbsp;command line&amp;nbsp;switches and output to a single combined file:&lt;br /&gt;
&lt;br /&gt;
&lt;code&gt;$java -jar compiler.jar --js in1.js --js in2.js &amp;gt; out.js&lt;/code&gt;&lt;br /&gt;
&lt;br /&gt;
Closure Compiler has three level of optimization:&lt;br /&gt;
&lt;br /&gt;
&lt;ul&gt;&lt;li&gt;Remove WHITESPACE_ONLY - basically as per the venerable &lt;a href="http://www.crockford.com/javascript/jsmin.html"&gt;JSMin&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;SIMPLE_OPTIMIZATIONS - removes whitespace, shortens private variables and other provably safe changes to the code.&lt;/li&gt;
&lt;li&gt;ADVANCED_OPTIMIZATIONS - as per Simple&amp;nbsp;Optimization&amp;nbsp;but much more aggressive. This level relies on &lt;a href="http://en.wikipedia.org/wiki/JSDoc"&gt;JSDoc&lt;/a&gt; comments to help the compiler understand which optimizations are safe for the code.&lt;/li&gt;
&lt;/ul&gt;&lt;br /&gt;
Passing the desired level optimization to the compiler is another switch:&lt;br /&gt;
&lt;br /&gt;
&lt;code&gt;$java -jar --compilation_level WHITESPACE_ONLY&lt;/code&gt;&lt;br /&gt;
&lt;br /&gt;
You can read more about optimization levels and the differences &lt;a href="http://code.google.com/closure/compiler/docs/compilation_levels.html"&gt;here&lt;/a&gt;. Closure also helpfully provides warnings about poor or dangerous coding patterns. The&amp;nbsp;compiler can also be accessed as a remote RESTful API, but I have not played with that yet.&lt;br /&gt;
&lt;br /&gt;
Read the &lt;a href="http://code.google.com/closure/compiler/docs/overview.html"&gt;documentation&lt;/a&gt; for more information. For myself using closure compiler will likely be a gateway to learning about jsdoc,&amp;nbsp;JavaScript&amp;nbsp;code linting and better overall JavaScript coding. Awesome.&lt;br /&gt;
&lt;br /&gt;
But wait up. How do you work with JavaScript code once you have thoroughly minified and optimized it? The short answer is that you don't. In an upcoming blog entry I will explain how I brought Closure Compiler into my build process.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9174832840890796616-1891029204971953221?l=www.learningtechnicalstuff.com' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/LearningTechnicalStuff/~4/eF0kN4qdbeY" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://www.learningtechnicalstuff.com/feeds/1891029204971953221/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.learningtechnicalstuff.com/2010/12/using-closure-javascript-optimization.html#comment-form" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/9174832840890796616/posts/default/1891029204971953221?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/9174832840890796616/posts/default/1891029204971953221?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/LearningTechnicalStuff/~3/eF0kN4qdbeY/using-closure-javascript-optimization.html" title="Using Closure JavaScript Optimization and Minification" /><author><name>Ben Davies</name><uri>http://www.blogger.com/profile/15225977828752157616</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="29" height="32" src="http://4.bp.blogspot.com/_qLhNkiMU1-k/Sh_l4FTCGkI/AAAAAAAAAEY/ABoYTtyFXEA/S220/IMG_2414.jpg" /></author><thr:total>0</thr:total><feedburner:origLink>http://www.learningtechnicalstuff.com/2010/12/using-closure-javascript-optimization.html</feedburner:origLink></entry><entry gd:etag="W/&quot;A04MRn47eip7ImA9Wx9SFk4.&quot;"><id>tag:blogger.com,1999:blog-9174832840890796616.post-7287734924399037554</id><published>2010-12-06T04:53:00.000-08:00</published><updated>2010-12-06T04:53:07.002-08:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2010-12-06T04:53:07.002-08:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="Python" /><title>Link: Importing Python Modules</title><content type="html">I ran into &lt;a href="http://effbot.org/zone/import-confusion.htm"&gt;this blog post&lt;/a&gt; on importing Python Modules a couple of weeks ago and it help solidify my understanding of how &lt;code&gt;import&lt;/code&gt; and &lt;code&gt;from..import&lt;/code&gt; (and &lt;code&gt;__import__()&lt;/code&gt;) operate in Python and what they actually do.&lt;br /&gt;
&lt;div&gt;&lt;br /&gt;
&lt;/div&gt;&lt;div&gt;The &lt;a href="http://www.python.org/dev/peps/pep-0008/"&gt;PEP 0008 Python Style Guide&lt;/a&gt; does not state a preference for import style but this blog post is fairly blunt in recommending import (and its lower amount of name pollution) over other approaches when possible.&lt;br /&gt;
&lt;div&gt;&lt;br /&gt;
&lt;/div&gt;&lt;div&gt;A good, clear read. Thanks Fredrik!&lt;/div&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9174832840890796616-7287734924399037554?l=www.learningtechnicalstuff.com' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/LearningTechnicalStuff/~4/yqqasgagDao" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://www.learningtechnicalstuff.com/feeds/7287734924399037554/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.learningtechnicalstuff.com/2010/12/link-importing-python-modules.html#comment-form" title="1 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/9174832840890796616/posts/default/7287734924399037554?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/9174832840890796616/posts/default/7287734924399037554?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/LearningTechnicalStuff/~3/yqqasgagDao/link-importing-python-modules.html" title="Link: Importing Python Modules" /><author><name>Ben Davies</name><uri>http://www.blogger.com/profile/15225977828752157616</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="29" height="32" src="http://4.bp.blogspot.com/_qLhNkiMU1-k/Sh_l4FTCGkI/AAAAAAAAAEY/ABoYTtyFXEA/S220/IMG_2414.jpg" /></author><thr:total>1</thr:total><feedburner:origLink>http://www.learningtechnicalstuff.com/2010/12/link-importing-python-modules.html</feedburner:origLink></entry><entry gd:etag="W/&quot;CUACSH8yfCp7ImA9Wx9SFE4.&quot;"><id>tag:blogger.com,1999:blog-9174832840890796616.post-4343306990688976130</id><published>2010-12-03T19:36:00.000-08:00</published><updated>2010-12-03T19:36:09.194-08:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2010-12-03T19:36:09.194-08:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="PyDev" /><category scheme="http://www.blogger.com/atom/ns#" term="PyLint" /><category scheme="http://www.blogger.com/atom/ns#" term="Aptana Studio" /><title>Python Code Style</title><content type="html">Code conventions are important in software development. In Python, where whitespace (specifically indentation) can have a functional impact on your software, coding conventions are critical, especially when code needs to be shared and reused.  &lt;br /&gt;
&lt;br /&gt;
Python does have a widely accepted (and widely tweaked) Style Guide called &lt;a href="http://www.python.org/dev/peps/pep-0008/"&gt;PEP 8&lt;/a&gt;. I saw it a while ago but didn't have the time to digest and then laboriously apply it to my active codebases.&lt;br /&gt;
&lt;br /&gt;
When I recently rebooted my development environment on Ubuntu and Mac OSX I noticed the &lt;a href="http://pydev.org/"&gt;PyDev &lt;/a&gt;IDE (now shipping with &lt;a href="http://www.aptana.com/products/studio2"&gt;Aptana Studio&lt;/a&gt;) &amp;nbsp;had support for PyLint. &lt;a href="http://www.logilab.org/857"&gt;PyLint &lt;/a&gt;- like other 'linting' programs - can examine your code and find non-compliances to a specific standard of code formatting. PyLint, when used as commandline tool, can provide a detailed report on your code style. &lt;br /&gt;
&lt;br /&gt;
The great thing about the integration with PyDev, however, is that PyLint shows non-compliances in your currently open files and flags them as warnings and problems in your problems view. This makes deciding to implement and meet a code standard a lot less cognitively burdensome.&lt;br /&gt;
&lt;br /&gt;
PyLint also highlights bad code smells - components and practices which don't smell right that might need refactoring. PyLint can be given global instructions to ignore certain issues, or specific source files can include comments to change how PyLint analyses the file. This gives some ability to customise the behaviour and ignore PyLint where pragmatic.&lt;br /&gt;
&lt;br /&gt;
To enable PyLint in PyDev, first make sure PyLint is installed. Installing PyLint is as easy as installing any python program:&lt;br /&gt;
&lt;br /&gt;
&lt;code&gt;$sudo easy_install pylint&lt;/code&gt;&lt;br /&gt;
&lt;br /&gt;
If you have different python versions in your environment, use the correct invocation of SetupTools for that version. I need to target Python2.5 for Google App Engine, so I use:&lt;br /&gt;
&lt;br /&gt;
&lt;code&gt;$sudo easy_install-2.5 pylint&lt;/code&gt;&lt;br /&gt;
&lt;br /&gt;
Once pyLint is installed, within Eclipse (or Aptana etcetera) visit the Preferences page and specifically the PyDev &amp;gt; PyLint section in the&amp;nbsp;hierarchy. Locate the actual PyLint script (lint.py) and enable PyLint.&lt;br /&gt;
&lt;br /&gt;
PyLint is really helping me clean up my code (I admit it - I am a recovering tab-fiend).&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9174832840890796616-4343306990688976130?l=www.learningtechnicalstuff.com' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/LearningTechnicalStuff/~4/YPGCLaDnbcw" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://www.learningtechnicalstuff.com/feeds/4343306990688976130/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.learningtechnicalstuff.com/2010/12/python-code-style.html#comment-form" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/9174832840890796616/posts/default/4343306990688976130?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/9174832840890796616/posts/default/4343306990688976130?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/LearningTechnicalStuff/~3/YPGCLaDnbcw/python-code-style.html" title="Python Code Style" /><author><name>Ben Davies</name><uri>http://www.blogger.com/profile/15225977828752157616</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="29" height="32" src="http://4.bp.blogspot.com/_qLhNkiMU1-k/Sh_l4FTCGkI/AAAAAAAAAEY/ABoYTtyFXEA/S220/IMG_2414.jpg" /></author><thr:total>0</thr:total><feedburner:origLink>http://www.learningtechnicalstuff.com/2010/12/python-code-style.html</feedburner:origLink></entry><entry gd:etag="W/&quot;AkICQn06eyp7ImA9Wx9TFEU.&quot;"><id>tag:blogger.com,1999:blog-9174832840890796616.post-8557870162171616657</id><published>2010-11-22T21:02:00.000-08:00</published><updated>2010-11-22T21:02:43.313-08:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2010-11-22T21:02:43.313-08:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="Mac OS" /><category scheme="http://www.blogger.com/atom/ns#" term="Git" /><title>Git Mac OSX Binaries from Google Code</title><content type="html">I was recently in Melbourne attending the &lt;a href="http://www.cfobjective.com.au/"&gt;cf.Objective() ANZ conference&lt;/a&gt; and was hopeful of getting some further setup done on my MacBook Air to make it ready for prime-time development. This will be my third development environment in use, after Ubuntu and Windows 7.&lt;br /&gt;
&lt;br /&gt;
I wanted to jump to distributed version control using &lt;a href="http://git-scm.com/"&gt;Git&lt;/a&gt; (a DVCS being essential for offline development) but found that both compiling from &lt;a href="http://kernel.org/pub/software/scm/git/git-1.7.3.2.tar.bz2"&gt;source&lt;/a&gt; and using &lt;a href="http://draft.blogger.com/"&gt;&lt;span id="goog_906384725"&gt;&lt;/span&gt;MacPorts&lt;span id="goog_906384726"&gt;&lt;/span&gt;&lt;/a&gt; require a version of the C compiler not installed on my MacBook Air. Worse, the way to get the C compiler was to install Xcode. &lt;a href="http://developer.apple.com/technologies/tools/xcode.html"&gt;Xcode&lt;/a&gt;, if you didn't know (I didn't), is a hefty 2.9 GB download from Apple containing their development platforms.&lt;br /&gt;
&lt;br /&gt;
Luckily I came across &lt;a href="http://code.google.com/p/git-osx-installer/downloads/list?can=3"&gt;maintained binaries for Git hosted on Google Code&lt;/a&gt;, so the XCode install on my limited-space Air is not required. The binaries are working well. Thanks to the maintainers for keeping this out there.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9174832840890796616-8557870162171616657?l=www.learningtechnicalstuff.com' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/LearningTechnicalStuff/~4/AGgqMhziyuo" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://www.learningtechnicalstuff.com/feeds/8557870162171616657/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.learningtechnicalstuff.com/2010/11/git-mac-osx-binaries-from-google-code.html#comment-form" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/9174832840890796616/posts/default/8557870162171616657?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/9174832840890796616/posts/default/8557870162171616657?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/LearningTechnicalStuff/~3/AGgqMhziyuo/git-mac-osx-binaries-from-google-code.html" title="Git Mac OSX Binaries from Google Code" /><author><name>Ben Davies</name><uri>http://www.blogger.com/profile/15225977828752157616</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="29" height="32" src="http://4.bp.blogspot.com/_qLhNkiMU1-k/Sh_l4FTCGkI/AAAAAAAAAEY/ABoYTtyFXEA/S220/IMG_2414.jpg" /></author><thr:total>0</thr:total><feedburner:origLink>http://www.learningtechnicalstuff.com/2010/11/git-mac-osx-binaries-from-google-code.html</feedburner:origLink></entry><entry gd:etag="W/&quot;DkYBRnwyfyp7ImA9Wx5aE0g.&quot;"><id>tag:blogger.com,1999:blog-9174832840890796616.post-7636885169679989931</id><published>2010-11-09T17:55:00.000-08:00</published><updated>2010-11-09T17:55:57.297-08:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2010-11-09T17:55:57.297-08:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="Mac OS" /><title>Changing the Hostname on Mac OSX</title><content type="html">My new &lt;a href="http://store.apple.com/us/browse/home/shop_mac/family/macbook_air"&gt;MacBook Air&lt;/a&gt; is my first Mac OS device, so I have some learning to do to come up to speed with the new environment. I've noticed that the OS setup routine at the start has set a daft hostname (containing my full name) so as a matter of priority I wanted to change it.&lt;br /&gt;
&lt;br /&gt;
To change the hostname on a Mac OS X machine, use the following:&lt;br /&gt;
&lt;br /&gt;
&lt;code&gt;sudo scutil --set HostName &lt;em&gt;mimas&lt;/em&gt;.local&lt;/code&gt;&lt;br /&gt;
&lt;br /&gt;
where 'mimas' is the required hostname&lt;br /&gt;
&lt;br /&gt;
I found this tidbit in a &lt;a href="http://blog.psyrendust.com/2008/05/23/change-the-hostname-in-mac-os-x-osx/"&gt;blog post&lt;/a&gt; (thanks Larry!)&lt;br /&gt;
&lt;br /&gt;
You might see a few of these OS X related posts, sorry, as I come to grips with it all. I promise to graduate from the elementary stuff as soon as possible :)&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9174832840890796616-7636885169679989931?l=www.learningtechnicalstuff.com' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/LearningTechnicalStuff/~4/HUb2UF0aXqY" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://www.learningtechnicalstuff.com/feeds/7636885169679989931/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.learningtechnicalstuff.com/2010/11/changing-hostname-on-mac-osx.html#comment-form" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/9174832840890796616/posts/default/7636885169679989931?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/9174832840890796616/posts/default/7636885169679989931?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/LearningTechnicalStuff/~3/HUb2UF0aXqY/changing-hostname-on-mac-osx.html" title="Changing the Hostname on Mac OSX" /><author><name>Ben Davies</name><uri>http://www.blogger.com/profile/15225977828752157616</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="29" height="32" src="http://4.bp.blogspot.com/_qLhNkiMU1-k/Sh_l4FTCGkI/AAAAAAAAAEY/ABoYTtyFXEA/S220/IMG_2414.jpg" /></author><thr:total>0</thr:total><feedburner:origLink>http://www.learningtechnicalstuff.com/2010/11/changing-hostname-on-mac-osx.html</feedburner:origLink></entry><entry gd:etag="W/&quot;AkUFRXwyfSp7ImA9Wx5aEko.&quot;"><id>tag:blogger.com,1999:blog-9174832840890796616.post-1182285780867077422</id><published>2010-11-08T20:49:00.000-08:00</published><updated>2010-11-08T20:50:14.295-08:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2010-11-08T20:50:14.295-08:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="Subclipse" /><category scheme="http://www.blogger.com/atom/ns#" term="Google App Engine" /><category scheme="http://www.blogger.com/atom/ns#" term="Subversion" /><category scheme="http://www.blogger.com/atom/ns#" term="PyDev" /><category scheme="http://www.blogger.com/atom/ns#" term="Eclipse" /><category scheme="http://www.blogger.com/atom/ns#" term="Aptana Studio" /><title>Quick Note: Eclipse and Subclipse on Ubuntu (for Appengine)</title><content type="html">I have &lt;a href="http://www.learningtechnicalstuff.com/2009/07/running-appdevserverpy-from-pydev.html"&gt;previously&lt;/a&gt; &lt;a href="http://www.learningtechnicalstuff.com/2009/07/setting-up-python-google-app-engine.html"&gt;written&lt;/a&gt; about configuring PyDev on top of an Eclipse/Aptana installation for Google App Engine development. Now on the Ubuntu platform instead of the windows, the process is easier these days since Pydev ships with the new Aptana Studio 3 Beta.&lt;br /&gt;
&lt;br /&gt;
I thought I would write a quick note to future Ben (i.e. me) about configuring Eclipse and &lt;a href="http://subclipse.tigris.org/"&gt;Subclipse&lt;/a&gt; on Ubuntu. Subclipse is a &lt;a href="http://subversion.apache.org/"&gt;Subversion&lt;/a&gt; plugin for Eclipse (the alternative is &lt;a href="http://www.eclipse.org/subversive/"&gt;Subversive&lt;/a&gt;, which I haven't tried).&lt;br /&gt;
&lt;br /&gt;
Briefly my Eclipse install (for reference):&lt;br /&gt;
&lt;br /&gt;
&lt;ul&gt;&lt;li&gt;&lt;code&gt;sudo apt-get install eclipse &lt;/code&gt;(if using eclipse base install)&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.aptana.com/products/studio3/download"&gt;Install Aptana Studio 3 beta&lt;/a&gt;, either standalone or as Eclipse plugin&lt;/li&gt;
&lt;li&gt;Install Google plugin for Eclipse from within Eclipse (&lt;a href="http://code.google.com/eclipse/docs/download.html"&gt;page with download sites&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Configure Python 2.5 interpreter within Eclipse (assuming you have already installed Python 2.5)&lt;/li&gt;
&lt;li&gt;Create new Google App Engine project (under PyDev grouping)&lt;/li&gt;
&lt;li&gt;&lt;a href="http://code.google.com/appengine/articles/eclipse.html"&gt;Configure Python Library Paths&lt;/a&gt;&amp;nbsp;for Eclipse&lt;/li&gt;
&lt;/ul&gt;To add Subclipse:&lt;br /&gt;
&lt;ul&gt;&lt;li&gt;&lt;code&gt;sudo apt-get install subversion&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;sudo apt-get install libsvn-java&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;install subclipse within Eclipse (&lt;a href="http://subclipse.tigris.org/servlets/ProjectProcess?pageID=p4wYuA"&gt;download sites&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;Since I eventually managed to install Python 2.5 I've had a great experience installing software on Ubuntu. I've experienced some detours but none of them have been too long. Let me know if I have missed anything, or could have done something better.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9174832840890796616-1182285780867077422?l=www.learningtechnicalstuff.com' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/LearningTechnicalStuff/~4/1qgVW23i1qg" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://www.learningtechnicalstuff.com/feeds/1182285780867077422/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.learningtechnicalstuff.com/2010/11/quick-note-eclipse-and-subclipse-on.html#comment-form" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/9174832840890796616/posts/default/1182285780867077422?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/9174832840890796616/posts/default/1182285780867077422?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/LearningTechnicalStuff/~3/1qgVW23i1qg/quick-note-eclipse-and-subclipse-on.html" title="Quick Note: Eclipse and Subclipse on Ubuntu (for Appengine)" /><author><name>Ben Davies</name><uri>http://www.blogger.com/profile/15225977828752157616</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="29" height="32" src="http://4.bp.blogspot.com/_qLhNkiMU1-k/Sh_l4FTCGkI/AAAAAAAAAEY/ABoYTtyFXEA/S220/IMG_2414.jpg" /></author><thr:total>0</thr:total><feedburner:origLink>http://www.learningtechnicalstuff.com/2010/11/quick-note-eclipse-and-subclipse-on.html</feedburner:origLink></entry><entry gd:etag="W/&quot;CEYCRX46fCp7ImA9Wx5aEk0.&quot;"><id>tag:blogger.com,1999:blog-9174832840890796616.post-455872468865716661</id><published>2010-11-07T23:42:00.000-08:00</published><updated>2010-11-07T23:42:44.014-08:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2010-11-07T23:42:44.014-08:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="Google App Engine" /><category scheme="http://www.blogger.com/atom/ns#" term="OpenID" /><category scheme="http://www.blogger.com/atom/ns#" term="he3-appengine-lib" /><title>Using the provided OpenID functionality on Google App Engine</title><content type="html">I've just released a small, simple application to &lt;a href="http://code.google.com/p/he3-appengine-lib/"&gt;he3-appengine-lib&lt;/a&gt; demonstrate using the provided &lt;a href="http://openid.net/"&gt;OpenID&lt;/a&gt; support Google added to App Engine in version X. I wrote the application from scratch to learn how a real application would behave, as I wasn't quite following the &lt;a href="http://code.google.com/appengine/articles/openid.html"&gt;documentation&lt;/a&gt;.&lt;br /&gt;
&lt;br /&gt;
When you set the authentication option for your application to OpenID to behaves very similiar to using the standard Google authentication. When you use Google authentication you redirect your users to the URL created by create_login_url() to login and create_logout_url() to log out. Before and after you can use the rest of the &lt;a href="http://code.google.com/appengine/docs/python/users/"&gt;google.appengine.api.users API&lt;/a&gt; to find out the current logged in user, their nickname and email address.&lt;br /&gt;
&lt;br /&gt;
Using OpenID works the same way with a only a few caveats:&lt;br /&gt;
&lt;br /&gt;
&lt;ul&gt;&lt;li&gt;When calling create_login_url(), you need to pass the required OpenID service provider or actual OpenID URL. This is because the the service provider provides the login screen (if required - the user might already be signed in).&lt;/li&gt;
&lt;li&gt;Some service providers might not supply an email address, which may be problematic depending on your requirements. Even if you don't need to email the user, the email address is a dependable identifier, whereas nickname is not. For example, MyOpenID.com does not supply an email address by default, although this can configured &amp;nbsp;at the time of the login. In my demonstration application, I detect when an email address has not be provided,. and reject the login.&lt;/li&gt;
&lt;li&gt;If you use &lt;a href="http://code.google.com/appengine/docs/python/config/appconfig.html#Requiring_Login_or_Administrator_Status"&gt;directives in the app.yaml&lt;/a&gt; file to mandate that login or admins are required for particular pages, you must implement a handler for /_ah/login and /_ah/login_required. Standard approach would direct the user to your dedicated login page (but I took the easy way out in my application and redirected to the home page)&lt;/li&gt;
&lt;/ul&gt;&lt;div&gt;Overall the transition from Google authentication to OpenID is not challenging from a &amp;nbsp;technical standpoint. As Google themselves discuss, some of the challenges arrive from simple usability choices in how you communicate to your users what their options are.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;
&lt;/div&gt;&lt;div&gt;You can find my demonstration application on &lt;a href="http://code.google.com/p/he3-appengine-lib/"&gt;he3-appengine-lib&lt;/a&gt;, and you can find a working copy at &lt;a href="http://he3-openidtest.appspot.com/"&gt;he3-openidtest.appspot.com&lt;/a&gt;, although I can't&amp;nbsp;guarantee&amp;nbsp;I will keep it around forever.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;
&lt;/div&gt;&lt;div&gt;I am yet to use the OpenID support in a production application, and I am sure I will learn more when I do. I'll record anything else of use that I find out here.&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9174832840890796616-455872468865716661?l=www.learningtechnicalstuff.com' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/LearningTechnicalStuff/~4/FxVljQeGN5E" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://www.learningtechnicalstuff.com/feeds/455872468865716661/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.learningtechnicalstuff.com/2010/11/using-provided-openid-functionality-on.html#comment-form" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/9174832840890796616/posts/default/455872468865716661?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/9174832840890796616/posts/default/455872468865716661?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/LearningTechnicalStuff/~3/FxVljQeGN5E/using-provided-openid-functionality-on.html" title="Using the provided OpenID functionality on Google App Engine" /><author><name>Ben Davies</name><uri>http://www.blogger.com/profile/15225977828752157616</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="29" height="32" src="http://4.bp.blogspot.com/_qLhNkiMU1-k/Sh_l4FTCGkI/AAAAAAAAAEY/ABoYTtyFXEA/S220/IMG_2414.jpg" /></author><thr:total>0</thr:total><feedburner:origLink>http://www.learningtechnicalstuff.com/2010/11/using-provided-openid-functionality-on.html</feedburner:origLink></entry><entry gd:etag="W/&quot;D0EERnszfCp7ImA9Wx5bGEo.&quot;"><id>tag:blogger.com,1999:blog-9174832840890796616.post-4358972184517003479</id><published>2010-11-04T06:00:00.000-07:00</published><updated>2010-11-04T06:00:07.584-07:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2010-11-04T06:00:07.584-07:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="Google App Engine" /><category scheme="http://www.blogger.com/atom/ns#" term="mapreduce" /><title>Mapreduce to be built in to Google App Engine Environment</title><content type="html">If you read the &lt;a href="http://code.google.com/p/googleappengine/wiki/SdkReleaseNotes#Version_1.3.8_-_October_14,_2010"&gt;release notes&lt;/a&gt; for the latest Google App Engine python SDK (1.3.8) you might have noticed the available declarations in the app.yaml application configuration has been increased. There are some interesting additions.&lt;br /&gt;
&lt;br /&gt;
The &lt;code&gt;include&lt;/code&gt; directive allows the app.yaml file to be broken apart for better reuse. Within the one project this might not make sense, but you could imagine entire utility modules suddenly being a lot more transportable between projects and easily added to an existing application.&lt;br /&gt;
&lt;br /&gt;
The &lt;code&gt;builtin&lt;/code&gt; directive, built on top the include functionality, provides easy inclusion of built-in handlers, including appstats, the administration console, the remote API and a 'datastore_admin'.&lt;br /&gt;
&lt;br /&gt;
The 'datastore_admin' builtin seems to be built on the &lt;a href="http://code.google.com/p/appengine-mapreduce/"&gt;appengine-mapreduce project&lt;/a&gt;. As I have &lt;a href="http://www.learningtechnicalstuff.com/2010/06/maintaining-app-engine-datastore-with.html"&gt;previously posted&lt;/a&gt;, MapReduce is a methodology of dividing a large task into smaller pieces of work that can be performed in parallel, before being reduced to a single a outcome.&lt;br /&gt;
&lt;br /&gt;
I am excited about the new builtin - since I discovered appengine-mapreduce I have considered it essential for maintaining a large set of data in the datastore.&lt;br /&gt;
&lt;br /&gt;
In case you were excited to try out the new Mapreduce functionality, you need to wait a little longer - until the next release of the SDK at least. Oddly enough the &lt;a href="http://code.google.com/appengine/docs/python/config/appconfig.html#Builtin_Handlers"&gt;documentation&lt;/a&gt; lists the 'datastore_admin' as a valid builtin option, and the libraries are now included in the SDK, but some import errors prevent it from being used (namely simplejson and graphy.backends).&lt;br /&gt;
&lt;br /&gt;
As this &lt;a href="http://groups.google.com/group/google-appengine-python/browse_thread/thread/fed03d52c478346d/"&gt;discussion thread&lt;/a&gt; on the Google Group shows, it isn't quite ready for primetime yet. Hopefully the next release will tidy things up.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9174832840890796616-4358972184517003479?l=www.learningtechnicalstuff.com' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/LearningTechnicalStuff/~4/8PHC7TfOOY0" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://www.learningtechnicalstuff.com/feeds/4358972184517003479/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.learningtechnicalstuff.com/2010/11/mapreduce-to-be-built-in-to-google-app.html#comment-form" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/9174832840890796616/posts/default/4358972184517003479?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/9174832840890796616/posts/default/4358972184517003479?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/LearningTechnicalStuff/~3/8PHC7TfOOY0/mapreduce-to-be-built-in-to-google-app.html" title="Mapreduce to be built in to Google App Engine Environment" /><author><name>Ben Davies</name><uri>http://www.blogger.com/profile/15225977828752157616</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="29" height="32" src="http://4.bp.blogspot.com/_qLhNkiMU1-k/Sh_l4FTCGkI/AAAAAAAAAEY/ABoYTtyFXEA/S220/IMG_2414.jpg" /></author><thr:total>0</thr:total><feedburner:origLink>http://www.learningtechnicalstuff.com/2010/11/mapreduce-to-be-built-in-to-google-app.html</feedburner:origLink></entry><entry gd:etag="W/&quot;DEABRXsyeyp7ImA9Wx5bFUo.&quot;"><id>tag:blogger.com,1999:blog-9174832840890796616.post-7770737827184847934</id><published>2010-10-31T18:59:00.000-07:00</published><updated>2010-10-31T18:59:14.593-07:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2010-10-31T18:59:14.593-07:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="Google App Engine" /><category scheme="http://www.blogger.com/atom/ns#" term="Ubuntu" /><title>Installing Python Google Appengine SDK on Ubuntu 10.10</title><content type="html">I am now back from my long hiatus from this blog (my excuse was a busy work schedule, wedding preparations and then the honeymoon) and I decided to have a look at Ubuntu 10.10. &lt;a href="http://www.ubuntu.com/"&gt;Ubuntu&lt;/a&gt; is a popular desktop Linux distribution which offers greater distribution-wide integration, consistency and polish than many other distributions.&lt;br /&gt;
&lt;div&gt;&lt;br /&gt;
&lt;/div&gt;&lt;div&gt;I have a long but not terribly recent history of trying Linux but never fully adopting it. As an exercise I wondered how feasible it would be to port my development environment for Python Google App Engine (GAE) to my new Ubuntu virtual machine. Most things on a Linux platform are possible as long as you know what you are doing, but I don't, so getting GAE running required some investigation.&lt;br /&gt;
&lt;br /&gt;
Ubuntu - at least as installed by the my VMware Easy Install process - presented some challenges. Ubuntu no longer installs the correct version of Python, for example, nor does it install source headers for libraries some Python extensions required by the SDK need to compile. After &lt;a href="http://groups.google.com/group/google-appengine-python/browse_thread/thread/78334a1a13316c77/"&gt;discussing my problem&lt;/a&gt; on the Google App Engine forums I found two very helpful blog posts that present the two different approaches to setting up Google App Engine Python SDK under Ubuntu 10.01.&lt;br /&gt;
&lt;br /&gt;
&lt;a name='more'&gt;&lt;/a&gt;&lt;br /&gt;
Both approaches achieve the same thing. &lt;a href="http://code.google.com/p/relat/wiki/python25"&gt;The harder approach&lt;/a&gt; (well, longer) is to download the Python&amp;nbsp;2.5 and related sources and install them manually. The &lt;a href="http://www.boris.co/2010/10/python-25-in-ubuntu-maverick.html"&gt;easier (and shorter) approach&lt;/a&gt; is to download the maintained Python 2.5 packages from a third party and use setup tools to do to the rest.&lt;br /&gt;
&lt;br /&gt;
The following is my recipe which is mostly a tweaked version of the easier approach.&lt;br /&gt;
&lt;br /&gt;
&lt;ol&gt;&lt;li&gt;Create directory structure for Google App Engine SDK and Google App Engine projects. In the terminal:&lt;br /&gt;
&lt;br /&gt;
&lt;code&gt;cd ~&lt;br /&gt;
mkdir Projects&lt;br /&gt;
mkdir Projects/gae&lt;/code&gt;&lt;br /&gt;
&lt;br /&gt;
(I am using exactly the same directory structure here as Boris does in his shorter approach. As mundane as it is I appreciate his detail here; As a Linux newbie I often wonder where to put things or even if it really matters)&lt;br /&gt;
&lt;br /&gt;
&lt;/li&gt;
&lt;li&gt;Download and extract the latest &lt;a href="http://code.google.com/appengine/downloads.html"&gt;Google App Engine SDK for Python&lt;/a&gt; (currently at 1.3.8) to ~/Projects/gae/google_appengine&lt;br /&gt;
&lt;/li&gt;
&lt;li&gt;Install Python 2.5. In this step I use the DeadSnakes repository for a maintained package of Python 2.5. I could also download the Python 2.5 source and compile it, but as wdenouden's Longer Approach shows you need to take care to already have SSL and Sqlite installed.&lt;br /&gt;
&lt;br /&gt;
&lt;code&gt;sudo add-apt-repository ppa:fkrull/deadsnakes&lt;br /&gt;
sudo apt-get update&lt;br /&gt;
sudo apt-get install python2.5&lt;/code&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;/li&gt;
&lt;li&gt;Install Python Setuptools (ie easy-install) for Python 2.5&lt;br /&gt;
&lt;br /&gt;
&lt;code&gt;cd /tmp&lt;br /&gt;
wget http://pypi.python.org/packages/2.5/s/setuptools/setuptools-0.6c11-py2.5.egg&lt;br /&gt;
sudo sh setuptools-0.6c11-py2.5.egg&lt;br /&gt;
&lt;/code&gt;&lt;br /&gt;
&lt;/li&gt;
&lt;li&gt;(optional) if you need the GAE imaging API, you will need to install PIL. PIL needs to be compiled for the local system, so it needs both the Python 2.5 headers and the headers for the JPEG library installed on ubuntu:&lt;br /&gt;
&lt;br /&gt;
&lt;code&gt;sudo apt-get install python2.5-dev&lt;br /&gt;
sudo apt-get install libjpeg62-dev&lt;br /&gt;
sudo easy_install-2.5 pil&lt;/code&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;/li&gt;
&lt;li&gt;Create a simple shell script to run the local development server and mark it executable by all. We'll put it firstly into the guestbook demo folder, test it and then copy it to the new_project_template.&lt;br /&gt;
&lt;br /&gt;
&lt;code&gt;cd ~/Projects/gae/google_appengine/demos/guestbook&lt;br /&gt;
echo "#!/bin/bash&lt;br /&gt;
python2.5 ~/Projects/gae/google_appengine/dev_appserver.py ./" &amp;gt; run&lt;br /&gt;
chmod a+x run&lt;/code&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;/li&gt;
&lt;li&gt;Test the new file and our Google App Engine SDK install&lt;br /&gt;
&lt;br /&gt;
&lt;code&gt;./run&lt;/code&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;/li&gt;
&lt;li&gt;Assuming all works well, copy the shell script to the new project template:&lt;br /&gt;
&lt;br /&gt;
&lt;code&gt;cp ./run /home/ben/Projects/gae/google_appengine/new_project_template/&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;That is my pass at a recipe for getting the Google App Engine Python SDK ready to go on Ubuntu 10.10. If you see something I have missed or a better way of achieving the same thing please let me know in the comments. &lt;br /&gt;
&lt;br /&gt;
&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9174832840890796616-7770737827184847934?l=www.learningtechnicalstuff.com' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/LearningTechnicalStuff/~4/_hVc1lIIDOc" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://www.learningtechnicalstuff.com/feeds/7770737827184847934/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.learningtechnicalstuff.com/2010/10/installing-python-google-appengine-sdk.html#comment-form" title="12 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/9174832840890796616/posts/default/7770737827184847934?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/9174832840890796616/posts/default/7770737827184847934?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/LearningTechnicalStuff/~3/_hVc1lIIDOc/installing-python-google-appengine-sdk.html" title="Installing Python Google Appengine SDK on Ubuntu 10.10" /><author><name>Ben Davies</name><uri>http://www.blogger.com/profile/15225977828752157616</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="29" height="32" src="http://4.bp.blogspot.com/_qLhNkiMU1-k/Sh_l4FTCGkI/AAAAAAAAAEY/ABoYTtyFXEA/S220/IMG_2414.jpg" /></author><thr:total>12</thr:total><feedburner:origLink>http://www.learningtechnicalstuff.com/2010/10/installing-python-google-appengine-sdk.html</feedburner:origLink></entry><entry gd:etag="W/&quot;D08GQnw5eyp7ImA9Wx9TFUg.&quot;"><id>tag:blogger.com,1999:blog-9174832840890796616.post-6140330908768059398</id><published>2010-08-24T23:07:00.000-07:00</published><updated>2010-11-23T15:43:43.223-08:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2010-11-23T15:43:43.223-08:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="Google App Engine" /><category scheme="http://www.blogger.com/atom/ns#" term="Python" /><title>Adding Library Paths to Google App Engine Python Projects</title><content type="html">Apologies - It has been a while since my last post in this blog. Wedding preparations and a focus on design work for a new version of &lt;a href="http://mywebbrain.com/"&gt;My Web Brain&lt;/a&gt; (along with my day job as a SQL developer on a SAP rollout) has kept me busy.&lt;br /&gt;
&lt;br /&gt;
Did you know you could alter the system path dynamically on Google App Engine Python? This tip is something that I am sure old hands at Python consider basic, but I hadn't seen the technique done before.&lt;br /&gt;
&lt;br /&gt;
As part of some research of the next version of My Web Brain, I evaluated some Google App Engine frameworks, one of which was &lt;a href="http://www.tipfy.org/"&gt;Tipfy&lt;/a&gt;. Tipfy uses this technique front and centre in its &lt;a href="http://code.google.com/p/tipfy/source/browse/project/app/main.py"&gt;main.py file&lt;/a&gt; to alter the system path:&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;table id="src_table_0" style="-webkit-border-horizontal-spacing: 2px; -webkit-border-vertical-spacing: 2px; border-collapse: collapse; font-family: monospace; font-size: 12px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px; padding-bottom: 0px; padding-left: 0px; padding-right: 0px; padding-top: 0px; white-space: pre;"&gt;&lt;tbody style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px; padding-bottom: 0px; padding-left: 0px; padding-right: 0px; padding-top: 0px;"&gt;
&lt;tr id="sl_svn9e7313a4d64da71062a7cc6294883d5f6e853845_11" style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px; padding-bottom: 0px; padding-left: 0px; padding-right: 0px; padding-top: 0px;"&gt;&lt;td class="source" style="font-size: 12px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px; padding-bottom: 0px; padding-left: 4px; padding-right: 0px; padding-top: 0px; vertical-align: top; white-space: pre-wrap;"&gt;&lt;span class="kwd" style="color: #000088;"&gt;import&lt;/span&gt;&lt;span class="pln" style="color: black;"&gt; os&lt;/span&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr id="sl_svn9e7313a4d64da71062a7cc6294883d5f6e853845_12" style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px; padding-bottom: 0px; padding-left: 0px; padding-right: 0px; padding-top: 0px;"&gt;&lt;td class="source" style="font-size: 12px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px; padding-bottom: 0px; padding-left: 4px; padding-right: 0px; padding-top: 0px; vertical-align: top; white-space: pre-wrap;"&gt;&lt;span class="kwd" style="color: #000088;"&gt;import&lt;/span&gt;&lt;span class="pln" style="color: black;"&gt; sys&lt;/span&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr id="sl_svn9e7313a4d64da71062a7cc6294883d5f6e853845_13" style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px; padding-bottom: 0px; padding-left: 0px; padding-right: 0px; padding-top: 0px;"&gt;&lt;td class="source" style="font-size: 12px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px; padding-bottom: 0px; padding-left: 4px; padding-right: 0px; padding-top: 0px; vertical-align: top; white-space: pre-wrap;"&gt;&lt;span class="pln" style="color: black;"&gt;&lt;br /&gt;
&lt;/span&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr id="sl_svn9e7313a4d64da71062a7cc6294883d5f6e853845_14" style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px; padding-bottom: 0px; padding-left: 0px; padding-right: 0px; padding-top: 0px;"&gt;&lt;td class="source" style="font-size: 12px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px; padding-bottom: 0px; padding-left: 4px; padding-right: 0px; padding-top: 0px; vertical-align: top; white-space: pre-wrap;"&gt;&lt;span class="kwd" style="color: #000088;"&gt;if&lt;/span&gt;&lt;span class="pln" style="color: black;"&gt; &lt;/span&gt;&lt;span class="str" style="color: #008800;"&gt;'lib'&lt;/span&gt;&lt;span class="pln" style="color: black;"&gt; &lt;/span&gt;&lt;span class="kwd" style="color: #000088;"&gt;not&lt;/span&gt;&lt;span class="pln" style="color: black;"&gt; &lt;/span&gt;&lt;span class="kwd" style="color: #000088;"&gt;in&lt;/span&gt;&lt;span class="pln" style="color: black;"&gt; sys&lt;/span&gt;&lt;span class="pun" style="color: #666600;"&gt;.&lt;/span&gt;&lt;span class="pln" style="color: black;"&gt;path&lt;/span&gt;&lt;span class="pun" style="color: #666600;"&gt;:&lt;/span&gt;&lt;span class="pln" style="color: black;"&gt;&lt;br /&gt;
&lt;/span&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr id="sl_svn9e7313a4d64da71062a7cc6294883d5f6e853845_15" style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px; padding-bottom: 0px; padding-left: 0px; padding-right: 0px; padding-top: 0px;"&gt;&lt;td class="source" style="font-size: 12px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px; padding-bottom: 0px; padding-left: 4px; padding-right: 0px; padding-top: 0px; vertical-align: top; white-space: pre-wrap;"&gt;&lt;span class="pln" style="color: black;"&gt;&amp;nbsp; &amp;nbsp; &lt;/span&gt;&lt;span class="com" style="color: #880000;"&gt;# Add /lib as primary libraries directory, with fallback to /distlib&lt;/span&gt;&lt;span class="pln" style="color: black;"&gt;&lt;br /&gt;
&lt;/span&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr id="sl_svn9e7313a4d64da71062a7cc6294883d5f6e853845_16" style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px; padding-bottom: 0px; padding-left: 0px; padding-right: 0px; padding-top: 0px;"&gt;&lt;td class="source" style="font-size: 12px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px; padding-bottom: 0px; padding-left: 4px; padding-right: 0px; padding-top: 0px; vertical-align: top; white-space: pre-wrap;"&gt;&lt;span class="pln" style="color: black;"&gt;&amp;nbsp; &amp;nbsp; &lt;/span&gt;&lt;span class="com" style="color: #880000;"&gt;# and optionally to distlib loaded using zipimport.&lt;/span&gt;&lt;span class="pln" style="color: black;"&gt;&lt;br /&gt;
&lt;/span&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr id="sl_svn9e7313a4d64da71062a7cc6294883d5f6e853845_17" style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px; padding-bottom: 0px; padding-left: 0px; padding-right: 0px; padding-top: 0px;"&gt;&lt;td class="source" style="font-size: 12px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px; padding-bottom: 0px; padding-left: 4px; padding-right: 0px; padding-top: 0px; vertical-align: top; white-space: pre-wrap;"&gt;&lt;span class="pln" style="color: black;"&gt;&amp;nbsp; &amp;nbsp; sys&lt;/span&gt;&lt;span class="pun" style="color: #666600;"&gt;.&lt;/span&gt;&lt;span class="pln" style="color: black;"&gt;path&lt;/span&gt;&lt;span class="pun" style="color: #666600;"&gt;[&lt;/span&gt;&lt;span class="lit" style="color: #006666;"&gt;0&lt;/span&gt;&lt;span class="pun" style="color: #666600;"&gt;:&lt;/span&gt;&lt;span class="lit" style="color: #006666;"&gt;0&lt;/span&gt;&lt;span class="pun" style="color: #666600;"&gt;]&lt;/span&gt;&lt;span class="pln" style="color: black;"&gt; &lt;/span&gt;&lt;span class="pun" style="color: #666600;"&gt;=&lt;/span&gt;&lt;span class="pln" style="color: black;"&gt; &lt;/span&gt;&lt;span class="pun" style="color: #666600;"&gt;[&lt;/span&gt;&lt;span class="str" style="color: #008800;"&gt;'lib'&lt;/span&gt;&lt;span class="pun" style="color: #666600;"&gt;,&lt;/span&gt;&lt;span class="pln" style="color: black;"&gt; &lt;/span&gt;&lt;span class="str" style="color: #008800;"&gt;'distlib'&lt;/span&gt;&lt;span class="pun" style="color: #666600;"&gt;,&lt;/span&gt;&lt;span class="pln" style="color: black;"&gt; &lt;/span&gt;&lt;span class="str" style="color: #008800;"&gt;'distlib.zip'&lt;/span&gt;&lt;span class="pun" style="color: #666600;"&gt;]&lt;/span&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;&lt;br /&gt;
&lt;br /&gt;
Taking control of the system path frees your package structure from the normal constraints of the project folder structure. And as the Tipfy code demonstrates, it also provides a good way of separating framework, vendor and customised code bases with the desired fallback order.&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;Update 24 Nov 2010:&lt;/b&gt; Just a note on what is obvious in hindsight. You need to update sys.path prior to importing from modules on the affected path(s). The same is true for modules with dependencies on the affected path.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9174832840890796616-6140330908768059398?l=www.learningtechnicalstuff.com' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/LearningTechnicalStuff/~4/6Z58WJ6-rK8" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://www.learningtechnicalstuff.com/feeds/6140330908768059398/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.learningtechnicalstuff.com/2010/08/adding-library-paths-to-google-app.html#comment-form" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/9174832840890796616/posts/default/6140330908768059398?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/9174832840890796616/posts/default/6140330908768059398?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/LearningTechnicalStuff/~3/6Z58WJ6-rK8/adding-library-paths-to-google-app.html" title="Adding Library Paths to Google App Engine Python Projects" /><author><name>Ben Davies</name><uri>http://www.blogger.com/profile/15225977828752157616</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="29" height="32" src="http://4.bp.blogspot.com/_qLhNkiMU1-k/Sh_l4FTCGkI/AAAAAAAAAEY/ABoYTtyFXEA/S220/IMG_2414.jpg" /></author><thr:total>0</thr:total><feedburner:origLink>http://www.learningtechnicalstuff.com/2010/08/adding-library-paths-to-google-app.html</feedburner:origLink></entry><entry gd:etag="W/&quot;DkINSHc7fyp7ImA9WxFbEUk.&quot;"><id>tag:blogger.com,1999:blog-9174832840890796616.post-3577206007314240989</id><published>2010-07-03T01:09:00.000-07:00</published><updated>2010-07-03T01:09:59.907-07:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2010-07-03T01:09:59.907-07:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="Google App Engine" /><title>Timezones using Gae-Pytz</title><content type="html">This is a short post about my good experiences moving from standard pytz to the gae-pytz library for Google App Engine. &lt;a href="http://pytz.sourceforge.net/"&gt;Pytz &lt;/a&gt;is the standard python library for defining timezone information. &lt;a href="http://pypi.python.org/pypi/gaepytz"&gt;Gae-pytz&lt;/a&gt; is a version of pytz optimised for &lt;a href="http://code.google.com/appengine/"&gt;Google App Engine&lt;/a&gt;.&lt;br /&gt;
&lt;br /&gt;
If you have read one of my &lt;a href="http://www.learningtechnicalstuff.com/2010/01/supporting-timezones-in-google-app.html"&gt;previous posts &lt;/a&gt;about implementing timezone support in Google App Engine, you'll know I have used the full pytz library in the past. In addition to improved performance, gae-pytz offers a couple of key advantages in comparison to vanilla pytz:&lt;br /&gt;
&lt;br /&gt;
&lt;ol&gt;&lt;li&gt;All timezone objects are packaged and deployed in a zip file. There are hundreds of timezone files in the normal pytz distribution, which makes the library particularly burdensome in the 1000-file maximum limit imposed by Google App Engine.&lt;/li&gt;
&lt;li&gt;Gae-pytz has built-in memcache caching of individual timezone objects.&lt;/li&gt;
&lt;/ol&gt;&lt;div&gt;My experience with implementing gae-pytz in &lt;a href="http://mywebbrain.com/"&gt;my application&lt;/a&gt; was great. For the most part it is a drop in replacement with no changes to code required. I could actually remove all of the caching code I had put in place since the provided caching already provided the functionality. If you have previously built an application with pytz for app engine, check gae-pytz out.&amp;nbsp;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9174832840890796616-3577206007314240989?l=www.learningtechnicalstuff.com' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/LearningTechnicalStuff/~4/PRl0uNl4bgQ" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://www.learningtechnicalstuff.com/feeds/3577206007314240989/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.learningtechnicalstuff.com/2010/07/timezones-using-gae-pytz.html#comment-form" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/9174832840890796616/posts/default/3577206007314240989?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/9174832840890796616/posts/default/3577206007314240989?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/LearningTechnicalStuff/~3/PRl0uNl4bgQ/timezones-using-gae-pytz.html" title="Timezones using Gae-Pytz" /><author><name>Ben Davies</name><uri>http://www.blogger.com/profile/15225977828752157616</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="29" height="32" src="http://4.bp.blogspot.com/_qLhNkiMU1-k/Sh_l4FTCGkI/AAAAAAAAAEY/ABoYTtyFXEA/S220/IMG_2414.jpg" /></author><thr:total>0</thr:total><feedburner:origLink>http://www.learningtechnicalstuff.com/2010/07/timezones-using-gae-pytz.html</feedburner:origLink></entry><entry gd:etag="W/&quot;C08AQXk7fSp7ImA9WxFUF04.&quot;"><id>tag:blogger.com,1999:blog-9174832840890796616.post-3844492527465635152</id><published>2010-06-28T06:30:00.000-07:00</published><updated>2010-06-28T06:30:40.705-07:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2010-06-28T06:30:40.705-07:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="Google App Engine" /><category scheme="http://www.blogger.com/atom/ns#" term="mapreduce" /><category scheme="http://www.blogger.com/atom/ns#" term="he3-appengine-lib" /><title>Maintaining the App Engine Datastore with MapReduce</title><content type="html">One of the items on the todo list for my application &lt;a href="http://mywebbrain.com/"&gt;My Web Brain&lt;/a&gt; is to add features which will require changes to the data model used by the application. Making changes to an application's data model in the App Engine datastore is sometimes more painful than it should be, since the developer remains responsible for ensuring that existing entities are made consistent with the new model.&lt;br /&gt;
&lt;br /&gt;
Having recently &lt;a href="http://blog.notdot.net/2010/05/Exploring-the-new-mapper-API"&gt;read&lt;/a&gt; about a&lt;a href="http://code.google.com/p/appengine-mapreduce/"&gt;ppengine-mapreduce&lt;/a&gt; I decided I would write a simple Mapper that looks for and fixes missing property values and invalid ReferenceProperty keys. You can find the ModelHygienceMapper (as I call it) at &lt;a href="http://code.google.com/p/he3-appengine-lib/"&gt;he3-appengine-lib&lt;/a&gt; in the new &lt;a href="http://code.google.com/p/he3-appengine-lib/source/browse/trunk/src/he3/db/tools/mappers.py"&gt;mappers.py&lt;/a&gt; file (some simple documentation coming soon).&lt;br /&gt;
&lt;br /&gt;
&lt;a name='more'&gt;&lt;/a&gt;The code for the ModelHygieneMapper that does the work very simple, and mostly worth listing in the event that someone can suggest an improved approach.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;div style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;"&gt;&lt;/div&gt;&lt;table id="src_table_0" style="-webkit-border-horizontal-spacing: 2px; -webkit-border-vertical-spacing: 2px; border-collapse: collapse; font-family: monospace; font-size: 12px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px; padding-bottom: 0px; padding-left: 0px; padding-right: 0px; padding-top: 0px; white-space: pre;"&gt;&lt;tbody style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px; padding-bottom: 0px; padding-left: 0px; padding-right: 0px; padding-top: 0px;"&gt;
&lt;tr id="sl_svn39_28" style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px; padding-bottom: 0px; padding-left: 0px; padding-right: 0px; padding-top: 0px;"&gt;&lt;td class="source" style="font-size: 12px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px; padding-bottom: 0px; padding-left: 4px; padding-right: 0px; padding-top: 0px; vertical-align: top; white-space: pre-wrap;"&gt;&lt;span class="pln" style="color: black;"&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &lt;/span&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr id="sl_svn39_29" style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px; padding-bottom: 0px; padding-left: 0px; padding-right: 0px; padding-top: 0px;"&gt;&lt;td class="source" style="font-size: 12px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px; padding-bottom: 0px; padding-left: 4px; padding-right: 0px; padding-top: 0px; vertical-align: top; white-space: pre-wrap;"&gt;&lt;span class="lit" style="color: #006666;"&gt;@staticmethod&lt;/span&gt;&lt;span class="pln" style="color: black;"&gt;&lt;br /&gt;
&lt;/span&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr id="sl_svn39_30" style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px; padding-bottom: 0px; padding-left: 0px; padding-right: 0px; padding-top: 0px;"&gt;&lt;td class="source" style="font-size: 12px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px; padding-bottom: 0px; padding-left: 4px; padding-right: 0px; padding-top: 0px; vertical-align: top; white-space: pre-wrap;"&gt;&lt;span class="kwd"&gt;  &lt;/span&gt;&lt;span class="kwd" style="color: #000088;"&gt;def&lt;/span&gt;&lt;span class="pln" style="color: black;"&gt; process&lt;/span&gt;&lt;span class="pun" style="color: #666600;"&gt;(&lt;/span&gt;&lt;span class="pln" style="color: black;"&gt;entity&lt;/span&gt;&lt;span class="pun" style="color: #666600;"&gt;):&lt;/span&gt;&lt;span class="pln" style="color: black;"&gt;&lt;br /&gt;
&lt;/span&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr id="sl_svn39_31" style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px; padding-bottom: 0px; padding-left: 0px; padding-right: 0px; padding-top: 0px;"&gt;&lt;td class="source" style="font-size: 12px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px; padding-bottom: 0px; padding-left: 4px; padding-right: 0px; padding-top: 0px; vertical-align: top; white-space: pre-wrap;"&gt;&lt;span class="pln" style="color: black;"&gt;&amp;nbsp; &lt;/span&gt;&lt;span class="str" style="color: #008800;"&gt;'''Checks and repairs model integrity of the passed entity &lt;/span&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr id="sl_svn39_32" style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px; padding-bottom: 0px; padding-left: 0px; padding-right: 0px; padding-top: 0px;"&gt;&lt;td class="source" style="font-size: 12px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px; padding-bottom: 0px; padding-left: 4px; padding-right: 0px; padding-top: 0px; vertical-align: top; white-space: pre-wrap;"&gt;&lt;span class="str" style="color: #008800;"&gt;&amp;nbsp; 1. Removes dangling references&lt;/span&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr id="sl_svn39_33" style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px; padding-bottom: 0px; padding-left: 0px; padding-right: 0px; padding-top: 0px;"&gt;&lt;td class="source" style="font-size: 12px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px; padding-bottom: 0px; padding-left: 4px; padding-right: 0px; padding-top: 0px; vertical-align: top; white-space: pre-wrap;"&gt;&lt;span class="str" style="color: #008800;"&gt;&amp;nbsp; 2. Sets undefined datastore values to the default&lt;/span&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr id="sl_svn39_34" style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px; padding-bottom: 0px; padding-left: 0px; padding-right: 0px; padding-top: 0px;"&gt;&lt;td class="source" style="font-size: 12px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px; padding-bottom: 0px; padding-left: 4px; padding-right: 0px; padding-top: 0px; vertical-align: top; white-space: pre-wrap;"&gt;&lt;span class="str" style="color: #008800;"&gt;&amp;nbsp; '''&lt;/span&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr id="sl_svn39_35" style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px; padding-bottom: 0px; padding-left: 0px; padding-right: 0px; padding-top: 0px;"&gt;&lt;td class="source" style="font-size: 12px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px; padding-bottom: 0px; padding-left: 4px; padding-right: 0px; padding-top: 0px; vertical-align: top; white-space: pre-wrap;"&gt;&lt;span class="pln" style="color: black;"&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &lt;/span&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr id="sl_svn39_36" style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px; padding-bottom: 0px; padding-left: 0px; padding-right: 0px; padding-top: 0px;"&gt;&lt;td class="source" style="font-size: 12px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px; padding-bottom: 0px; padding-left: 4px; padding-right: 0px; padding-top: 0px; vertical-align: top; white-space: pre-wrap;"&gt;&lt;span class="pln" style="color: black;"&gt;  props &lt;/span&gt;&lt;span class="pun" style="color: #666600;"&gt;=&lt;/span&gt;&lt;span class="pln" style="color: black;"&gt; &lt;/span&gt;&lt;span class="pun" style="color: #666600;"&gt;[&lt;/span&gt;&lt;span class="pln" style="color: black;"&gt;x &lt;/span&gt;&lt;span class="kwd" style="color: #000088;"&gt;for&lt;/span&gt;&lt;span class="pln" style="color: black;"&gt; x &lt;/span&gt;&lt;span class="kwd" style="color: #000088;"&gt;in&lt;/span&gt;&lt;span class="pln" style="color: black;"&gt; entity&lt;/span&gt;&lt;span class="pun" style="color: #666600;"&gt;.&lt;/span&gt;&lt;span class="pln" style="color: black;"&gt;__class__&lt;/span&gt;&lt;span class="pun" style="color: #666600;"&gt;.&lt;/span&gt;&lt;span class="pln" style="color: black;"&gt;__dict__&lt;/span&gt;&lt;span class="pun" style="color: #666600;"&gt;.&lt;/span&gt;&lt;span class="pln" style="color: black;"&gt;values&lt;/span&gt;&lt;span class="pun" style="color: #666600;"&gt;()\&lt;/span&gt;&lt;span class="pln" style="color: black;"&gt;&lt;br /&gt;
&lt;/span&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr id="sl_svn39_37" style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px; padding-bottom: 0px; padding-left: 0px; padding-right: 0px; padding-top: 0px;"&gt;&lt;td class="source" style="font-size: 12px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px; padding-bottom: 0px; padding-left: 4px; padding-right: 0px; padding-top: 0px; vertical-align: top; white-space: pre-wrap;"&gt;&lt;span class="pln" style="color: black;"&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span class="kwd" style="color: #000088;"&gt;if&lt;/span&gt;&lt;span class="pln" style="color: black;"&gt; isinstance&lt;/span&gt;&lt;span class="pun" style="color: #666600;"&gt;(&lt;/span&gt;&lt;span class="pln" style="color: black;"&gt;x&lt;/span&gt;&lt;span class="pun" style="color: #666600;"&gt;,&lt;/span&gt;&lt;span class="pln" style="color: black;"&gt; db&lt;/span&gt;&lt;span class="pun" style="color: #666600;"&gt;.&lt;/span&gt;&lt;span class="typ" style="color: #660066;"&gt;Property&lt;/span&gt;&lt;span class="pun" style="color: #666600;"&gt;)]&lt;/span&gt;&lt;span class="pln" style="color: black;"&gt;&lt;br /&gt;
&lt;/span&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr id="sl_svn39_38" style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px; padding-bottom: 0px; padding-left: 0px; padding-right: 0px; padding-top: 0px;"&gt;&lt;td class="source" style="font-size: 12px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px; padding-bottom: 0px; padding-left: 4px; padding-right: 0px; padding-top: 0px; vertical-align: top; white-space: pre-wrap;"&gt;&lt;span class="pln" style="color: black;"&gt;&amp;nbsp; changed &lt;/span&gt;&lt;span class="pun" style="color: #666600;"&gt;=&lt;/span&gt;&lt;span class="pln" style="color: black;"&gt; &lt;/span&gt;&lt;span class="kwd" style="color: #000088;"&gt;False&lt;/span&gt;&lt;span class="pln" style="color: black;"&gt;&lt;br /&gt;
&lt;/span&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr id="sl_svn39_39" style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px; padding-bottom: 0px; padding-left: 0px; padding-right: 0px; padding-top: 0px;"&gt;&lt;td class="source" style="font-size: 12px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px; padding-bottom: 0px; padding-left: 4px; padding-right: 0px; padding-top: 0px; vertical-align: top; white-space: pre-wrap;"&gt;&lt;span class="pln" style="color: black;"&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &lt;/span&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr id="sl_svn39_40" style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px; padding-bottom: 0px; padding-left: 0px; padding-right: 0px; padding-top: 0px;"&gt;&lt;td class="source" style="font-size: 12px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px; padding-bottom: 0px; padding-left: 4px; padding-right: 0px; padding-top: 0px; vertical-align: top; white-space: pre-wrap;"&gt;&lt;span class="pln" style="color: black;"&gt;&amp;nbsp; &lt;/span&gt;&lt;span class="kwd" style="color: #000088;"&gt;for&lt;/span&gt;&lt;span class="pln" style="color: black;"&gt; prop &lt;/span&gt;&lt;span class="kwd" style="color: #000088;"&gt;in&lt;/span&gt;&lt;span class="pln" style="color: black;"&gt; props&lt;/span&gt;&lt;span class="pun" style="color: #666600;"&gt;:&lt;/span&gt;&lt;span class="pln" style="color: black;"&gt;&lt;br /&gt;
&lt;/span&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr id="sl_svn39_41" style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px; padding-bottom: 0px; padding-left: 0px; padding-right: 0px; padding-top: 0px;"&gt;&lt;td class="source" style="font-size: 12px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px; padding-bottom: 0px; padding-left: 4px; padding-right: 0px; padding-top: 0px; vertical-align: top; white-space: pre-wrap;"&gt;&lt;span class="pln" style="color: black;"&gt;&amp;nbsp;   &lt;/span&gt;&lt;span class="kwd" style="color: #000088;"&gt;if&lt;/span&gt;&lt;span class="pln" style="color: black;"&gt; &lt;/span&gt;&lt;span class="kwd" style="color: #000088;"&gt;not&lt;/span&gt;&lt;span class="pln" style="color: black;"&gt; prop&lt;/span&gt;&lt;span class="pun" style="color: #666600;"&gt;.&lt;/span&gt;&lt;span class="pln" style="color: black;"&gt;get_value_for_datastore&lt;/span&gt;&lt;span class="pun" style="color: #666600;"&gt;(&lt;/span&gt;&lt;span class="pln" style="color: black;"&gt;entity&lt;/span&gt;&lt;span class="pun" style="color: #666600;"&gt;):&lt;/span&gt;&lt;span class="pln" style="color: black;"&gt;&lt;br /&gt;
&lt;/span&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr id="sl_svn39_42" style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px; padding-bottom: 0px; padding-left: 0px; padding-right: 0px; padding-top: 0px;"&gt;&lt;td class="source" style="font-size: 12px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px; padding-bottom: 0px; padding-left: 4px; padding-right: 0px; padding-top: 0px; vertical-align: top; white-space: pre-wrap;"&gt;&lt;span class="pln" style="color: black;"&gt;&amp;nbsp; &amp;nbsp;   prop&lt;/span&gt;&lt;span class="pun" style="color: #666600;"&gt;.&lt;/span&gt;&lt;span class="pln" style="color: black;"&gt;__set__&lt;/span&gt;&lt;span class="pun" style="color: #666600;"&gt;(&lt;/span&gt;&lt;span class="pln" style="color: black;"&gt;entity&lt;/span&gt;&lt;span class="pun" style="color: #666600;"&gt;,&lt;/span&gt;&lt;span class="pln" style="color: black;"&gt; prop&lt;/span&gt;&lt;span class="pun" style="color: #666600;"&gt;.&lt;/span&gt;&lt;span class="pln" style="color: black;"&gt;default_value&lt;/span&gt;&lt;span class="pun" style="color: #666600;"&gt;())&lt;/span&gt;&lt;span class="pln" style="color: black;"&gt;&lt;br /&gt;
&lt;/span&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr id="sl_svn39_43" style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px; padding-bottom: 0px; padding-left: 0px; padding-right: 0px; padding-top: 0px;"&gt;&lt;td class="source" style="font-size: 12px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px; padding-bottom: 0px; padding-left: 4px; padding-right: 0px; padding-top: 0px; vertical-align: top; white-space: pre-wrap;"&gt;&lt;span class="pln" style="color: black;"&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; changed &lt;/span&gt;&lt;span class="pun" style="color: #666600;"&gt;=&lt;/span&gt;&lt;span class="pln" style="color: black;"&gt; &lt;/span&gt;&lt;span class="kwd" style="color: #000088;"&gt;True&lt;/span&gt;&lt;span class="pln" style="color: black;"&gt; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr id="sl_svn39_44" style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px; padding-bottom: 0px; padding-left: 0px; padding-right: 0px; padding-top: 0px;"&gt;&lt;td class="source" style="font-size: 12px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px; padding-bottom: 0px; padding-left: 4px; padding-right: 0px; padding-top: 0px; vertical-align: top; white-space: pre-wrap;"&gt;&lt;span class="pln" style="color: black;"&gt;&amp;nbsp; &amp;nbsp; &lt;/span&gt;&lt;span class="kwd" style="color: #000088;"&gt;elif&lt;/span&gt;&lt;span class="pln" style="color: black;"&gt; isinstance&lt;/span&gt;&lt;span class="pun" style="color: #666600;"&gt;(&lt;/span&gt;&lt;span class="pln" style="color: black;"&gt;prop&lt;/span&gt;&lt;span class="pun" style="color: #666600;"&gt;,&lt;/span&gt;&lt;span class="pln" style="color: black;"&gt; db&lt;/span&gt;&lt;span class="pun" style="color: #666600;"&gt;.&lt;/span&gt;&lt;span class="typ" style="color: #660066;"&gt;ReferenceProperty&lt;/span&gt;&lt;span class="pun" style="color: #666600;"&gt;):&lt;/span&gt;&lt;span class="pln" style="color: black;"&gt;&lt;br /&gt;
&lt;/span&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr id="sl_svn39_45" style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px; padding-bottom: 0px; padding-left: 0px; padding-right: 0px; padding-top: 0px;"&gt;&lt;td class="source" style="font-size: 12px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px; padding-bottom: 0px; padding-left: 4px; padding-right: 0px; padding-top: 0px; vertical-align: top; white-space: pre-wrap;"&gt;&lt;span class="pln" style="color: black;"&gt;&amp;nbsp; &amp;nbsp;   i&lt;/span&gt;&lt;span class="kwd" style="color: #000088;"&gt;f&lt;/span&gt;&lt;span class="pln" style="color: black;"&gt; &lt;/span&gt;&lt;span class="kwd" style="color: #000088;"&gt;not&lt;/span&gt;&lt;span class="pln" style="color: black;"&gt; db&lt;/span&gt;&lt;span class="pun" style="color: #666600;"&gt;.&lt;/span&gt;&lt;span class="pln" style="color: black;"&gt;get&lt;/span&gt;&lt;span class="pun" style="color: #666600;"&gt;(&lt;/span&gt;&lt;span class="pln" style="color: black;"&gt;prop&lt;/span&gt;&lt;span class="pun" style="color: #666600;"&gt;.&lt;/span&gt;&lt;span class="pln" style="color: black;"&gt;get_value_for_datastore&lt;/span&gt;&lt;span class="pun" style="color: #666600;"&gt;(&lt;/span&gt;&lt;span class="pln" style="color: black;"&gt;entity&lt;/span&gt;&lt;span class="pun" style="color: #666600;"&gt;)):&lt;/span&gt;&lt;span class="pln" style="color: black;"&gt;&lt;br /&gt;
&lt;/span&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr id="sl_svn39_46" style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px; padding-bottom: 0px; padding-left: 0px; padding-right: 0px; padding-top: 0px;"&gt;&lt;td class="source" style="font-size: 12px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px; padding-bottom: 0px; padding-left: 4px; padding-right: 0px; padding-top: 0px; vertical-align: top; white-space: pre-wrap;"&gt;&lt;span class="pln" style="color: black;"&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;   prop&lt;/span&gt;&lt;span class="pun" style="color: #666600;"&gt;.&lt;/span&gt;&lt;span class="pln" style="color: black;"&gt;__set__&lt;/span&gt;&lt;span class="pun" style="color: #666600;"&gt;(&lt;/span&gt;&lt;span class="pln" style="color: black;"&gt;entity&lt;/span&gt;&lt;span class="pun" style="color: #666600;"&gt;,&lt;/span&gt;&lt;span class="pln" style="color: black;"&gt; &lt;/span&gt;&lt;span class="kwd" style="color: #000088;"&gt;None&lt;/span&gt;&lt;span class="pun" style="color: #666600;"&gt;)&lt;/span&gt;&lt;span class="pln" style="color: black;"&gt;&lt;br /&gt;
&lt;/span&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr id="sl_svn39_47" style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px; padding-bottom: 0px; padding-left: 0px; padding-right: 0px; padding-top: 0px;"&gt;&lt;td class="source" style="font-size: 12px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px; padding-bottom: 0px; padding-left: 4px; padding-right: 0px; padding-top: 0px; vertical-align: top; white-space: pre-wrap;"&gt;&lt;span class="pln" style="color: black;"&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; changed &lt;/span&gt;&lt;span class="pun" style="color: #666600;"&gt;=&lt;/span&gt;&lt;span class="pln" style="color: black;"&gt; &lt;/span&gt;&lt;span class="kwd" style="color: #000088;"&gt;True&lt;/span&gt;&lt;span class="pln" style="color: black;"&gt;&lt;br /&gt;
&lt;/span&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr id="sl_svn39_48" style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px; padding-bottom: 0px; padding-left: 0px; padding-right: 0px; padding-top: 0px;"&gt;&lt;td class="source" style="font-size: 12px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px; padding-bottom: 0px; padding-left: 4px; padding-right: 0px; padding-top: 0px; vertical-align: top; white-space: pre-wrap;"&gt;&lt;span class="pln" style="color: black;"&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &lt;/span&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr id="sl_svn39_49" style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px; padding-bottom: 0px; padding-left: 0px; padding-right: 0px; padding-top: 0px;"&gt;&lt;td class="source" style="font-size: 12px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px; padding-bottom: 0px; padding-left: 4px; padding-right: 0px; padding-top: 0px; vertical-align: top; white-space: pre-wrap;"&gt;&lt;span class="pln" style="color: black;"&gt;&amp;nbsp; &lt;/span&gt;&lt;span class="pln"&gt;&lt;span class="Apple-style-span" style="color: #000088;"&gt;if&lt;/span&gt;&lt;/span&gt;&lt;span class="pln" style="color: black;"&gt; changed&lt;/span&gt;&lt;span class="pun" style="color: #666600;"&gt;:&lt;/span&gt;&lt;span class="pln" style="color: black;"&gt; &lt;/span&gt;&lt;span class="kwd" style="color: #000088;"&gt;yield&lt;/span&gt;&lt;span class="pln" style="color: black;"&gt; op&lt;/span&gt;&lt;span class="pun" style="color: #666600;"&gt;.&lt;/span&gt;&lt;span class="pln" style="color: black;"&gt;db&lt;/span&gt;&lt;span class="pun" style="color: #666600;"&gt;.&lt;/span&gt;&lt;span class="typ" style="color: #660066;"&gt;Put&lt;/span&gt;&lt;span class="pun" style="color: #666600;"&gt;(&lt;/span&gt;&lt;span class="pln" style="color: black;"&gt;entity&lt;/span&gt;&lt;span class="pun" style="color: #666600;"&gt;)  &lt;/span&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The concept of Map Reduce and the App engine project is more interesting than my simple mapper. The Map Reduce is a framework for breaking up large problems into small parts that can be run in parallel (as &lt;a href="http://en.wikipedia.org/wiki/MapReduce"&gt;I read on Wikipedia&lt;/a&gt;). Appengine-mapreduce is a project that has begun the task of implementing this concept on Google App Engine for Python and Java.&lt;br /&gt;
&lt;br /&gt;
The work of using Map Reduce is in defining the mappers and reducers. Mappers perform work on the problem data in parallel, while Reducers (from what I understand), recombinate the outputs based on information in the original dataset. If you can structure a solution to your problem using this paradigm it becomes easier to distribute the work between computers and datacenters. For App Engine developers, Map Reduce is a way to traverse the datastore or other collection of data (for example, lines in a file) effectively without re-engineering the plumbing that allows you to do this.&lt;br /&gt;
&lt;br /&gt;
Using appengine-mapreduce in your application, like I did with ModelHygieneMapper, &amp;nbsp;is easy. Include the source code for the library in your application and add a URL mapping to your app.yaml file. Create a Mapper.yaml file which contains the configuration for the mapper or mappers you wish to use. If you haven't already, write or include the mapper. The &lt;a href="http://code.google.com/p/appengine-mapreduce/wiki/GettingStartedInPython"&gt;getting started guide for python&lt;/a&gt; explains this pretty well.&lt;br /&gt;
&lt;br /&gt;
I will be keeping an eye on appengine-mapreduce in the future for other tasks it will be appropriate for. In the meantime, I am happy that I had a way to implement ModelHygienceMapper without worrying about the plumbing. If you have any feedback about my mapper, please let me know.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9174832840890796616-3844492527465635152?l=www.learningtechnicalstuff.com' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/LearningTechnicalStuff/~4/jJRwdcSqbOg" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://www.learningtechnicalstuff.com/feeds/3844492527465635152/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.learningtechnicalstuff.com/2010/06/maintaining-app-engine-datastore-with.html#comment-form" title="2 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/9174832840890796616/posts/default/3844492527465635152?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/9174832840890796616/posts/default/3844492527465635152?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/LearningTechnicalStuff/~3/jJRwdcSqbOg/maintaining-app-engine-datastore-with.html" title="Maintaining the App Engine Datastore with MapReduce" /><author><name>Ben Davies</name><uri>http://www.blogger.com/profile/15225977828752157616</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="29" height="32" src="http://4.bp.blogspot.com/_qLhNkiMU1-k/Sh_l4FTCGkI/AAAAAAAAAEY/ABoYTtyFXEA/S220/IMG_2414.jpg" /></author><thr:total>2</thr:total><feedburner:origLink>http://www.learningtechnicalstuff.com/2010/06/maintaining-app-engine-datastore-with.html</feedburner:origLink></entry><entry gd:etag="W/&quot;AkcCSXs8eCp7ImA9WxFVFkU.&quot;"><id>tag:blogger.com,1999:blog-9174832840890796616.post-6840184967297691007</id><published>2010-06-16T04:34:00.000-07:00</published><updated>2010-06-16T04:34:28.570-07:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2010-06-16T04:34:28.570-07:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="Google App Engine" /><category scheme="http://www.blogger.com/atom/ns#" term="he3-appengine-lib" /><title>PrefetchingQuery: Prefetch Reference Properties and Parents in App Engine</title><content type="html">Back in January Nick Johnson from Google &lt;a href="http://blog.notdot.net/2010/01/ReferenceProperty-prefetching-in-App-Engine"&gt;wrote about the benefits of prefetching reference properties&lt;/a&gt; for a collection of App Engine datastore entities. Prefetching can dramatically reduce the amount of RPC calls&lt;span class="Apple-style-span" style="font-size: small;"&gt;*&lt;/span&gt; to the datastore by ensuring each entity referenced by a property is only retreived once.&lt;br /&gt;
&lt;br /&gt;
It has taken me a while but I finally got a chance to go back to Nick's blog entry and build some infrastructure around his code to make prefetching reference properties easier. I call the effort PrefetchingQuery and you can find it over at &lt;a href="http://code.google.com/p/he3-appengine-lib/"&gt;he3-appengine-lib&lt;/a&gt;&amp;nbsp;in &lt;a href="http://code.google.com/p/he3-appengine-lib/source/browse/trunk/src/he3/db/tower/performing.py"&gt;performing.py&lt;/a&gt;&lt;br /&gt;
&lt;br /&gt;
Like &lt;a href="http://www.learningtechnicalstuff.com/2010/04/pagedquery-easy-paging-using-cursors-on.html"&gt;PagedQuery&lt;/a&gt;, PrefetchingQuery is a facade for a normal db.Query or db.GqlQuery object. You can configure the PrefetchingQuery with the properties to prefetch in its constructor, with a class attribute on your model entity or let it default to prefetching all reference properties. For example&lt;br /&gt;
&lt;code&gt;&lt;br /&gt;
#create a prefetching query&lt;br /&gt;
myPrefetchingQuery = PrefetchingQuery( MyEntity.all(),&amp;nbsp;&lt;/code&gt;&lt;span class="Apple-style-span" style="font-family: monospace;"&gt;(MyEntity.myReferenceProp1, ))&lt;/span&gt;&lt;br /&gt;
&lt;br /&gt;
configures the PrefetchingQuery to prefetch the myReferenceProp1 property. Actual prefetching occurs when you call fetch() on the query.&lt;br /&gt;
&lt;a name='more'&gt;&lt;/a&gt;&lt;br /&gt;
Still with me? Here are some additional details.&lt;br /&gt;
&lt;br /&gt;
After you have instantiated your shiny new PrefetchingQuery, you can use it like the underlying db.Query or db.GqlQuery object. This means you call filter(), order() or ancestor() for db.Query or count() or cursor() for either &lt;a href="http://code.google.com/appengine/docs/python/datastore/queryclass.html"&gt;db.Query&lt;/a&gt; or &lt;a href="http://code.google.com/appengine/docs/python/datastore/gqlqueryclass.html"&gt;db.GqlQuery&lt;/a&gt;.&lt;br /&gt;
&lt;br /&gt;
You can use PrefetchingQuery with &lt;a href="http://code.google.com/p/he3-appengine-lib/wiki/PagedQuery"&gt;PagedQuery&lt;/a&gt; if you like, but you need to wrap your original query first with PrefetchingQuery and then with PagedQuery, like this:&lt;br /&gt;
&lt;br /&gt;
&lt;span class="Apple-style-span" style="font-family: monospace;"&gt;#create a paged, prefetching query&lt;/span&gt;&lt;br /&gt;
&lt;code&gt; myPagedPrefetchingQuery = PagedQuery(PrefetchingQuery( MyEntity.all(),&amp;nbsp;&lt;/code&gt;&lt;span class="Apple-style-span" style="font-family: monospace;"&gt;(MyEntity.myReferenceProp1, )), 10)&lt;/span&gt;&lt;br /&gt;
&lt;br /&gt;
PrefetchingQuery can also prefetch the Parent entity of your models. You request this by adding 'parent' (ie. the string) into your list of reference properties to prefetch. &lt;b&gt;But be warned&lt;/b&gt;: Setting the parent or an element manually uses a private/undocumented attribute _parent and &lt;i&gt;may break in future App Engine platform or SDK releases&lt;/i&gt;. See &lt;a href="http://groups.google.com/group/google-appengine-python/browse_thread/thread/781033bc9440bc7f/"&gt;this discussion&lt;/a&gt; for background information.&lt;br /&gt;
&lt;br /&gt;
Thanks and credit for the important code behind PrefetchingQuery goes to &lt;a href="http://blog.notdot.net/"&gt;Nick Johnson&lt;/a&gt;, whose code I only had to modify slightly for my packaging. &lt;a href="http://groups.google.com/groups/profile?enc_user=6GyohRAAAAAqBlGTRG8KPal8Ci_xYaNS"&gt;Ubaldo Huerta&lt;/a&gt; and his discussion with Nick about parent properties also helped out. &lt;br /&gt;
&lt;br /&gt;
Further documentation about Prefetching will appear in course on he3-appengine-lib, but for now, you should peruse the comments at the top of &lt;a href="http://code.google.com/p/he3-appengine-lib/source/browse/trunk/src/he3/db/tower/performing.py"&gt;performing.py&lt;/a&gt;, if you want to find out more.&lt;br /&gt;
&lt;br /&gt;
I welcome any bug reports or feature suggestions. Give it a spin.&lt;br /&gt;
&lt;br /&gt;
&lt;span class="Apple-style-span" style="font-size: x-small;"&gt;* the amount of performance benefit will depend on your situation and programming goals. Past performance is no&amp;nbsp;guarantee&amp;nbsp;of future performance benefits.&amp;nbsp;&lt;/span&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9174832840890796616-6840184967297691007?l=www.learningtechnicalstuff.com' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/LearningTechnicalStuff/~4/Xieg1Qan6g0" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://www.learningtechnicalstuff.com/feeds/6840184967297691007/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.learningtechnicalstuff.com/2010/06/prefetchingquery-prefetch-reference.html#comment-form" title="2 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/9174832840890796616/posts/default/6840184967297691007?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/9174832840890796616/posts/default/6840184967297691007?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/LearningTechnicalStuff/~3/Xieg1Qan6g0/prefetchingquery-prefetch-reference.html" title="PrefetchingQuery: Prefetch Reference Properties and Parents in App Engine" /><author><name>Ben Davies</name><uri>http://www.blogger.com/profile/15225977828752157616</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="29" height="32" src="http://4.bp.blogspot.com/_qLhNkiMU1-k/Sh_l4FTCGkI/AAAAAAAAAEY/ABoYTtyFXEA/S220/IMG_2414.jpg" /></author><thr:total>2</thr:total><feedburner:origLink>http://www.learningtechnicalstuff.com/2010/06/prefetchingquery-prefetch-reference.html</feedburner:origLink></entry><entry gd:etag="W/&quot;CEYCQ3g_eSp7ImA9WxFVEE0.&quot;"><id>tag:blogger.com,1999:blog-9174832840890796616.post-5334235490245032649</id><published>2010-06-08T06:02:00.000-07:00</published><updated>2010-06-08T06:02:42.641-07:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2010-06-08T06:02:42.641-07:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="Google Gadgets" /><title>GRC Password Google Gadget</title><content type="html">Recently I &lt;a href="http://www.learningtechnicalstuff.com/2010/05/disabling-caching-of-google-gadgets-for.html"&gt;mentioned&lt;/a&gt; I was doing some light experimentation with &lt;a href="http://www.google.com/webmasters/gadgets/"&gt;Google Gadgets&lt;/a&gt;. I decided to create a gadget to retrieve a password from &lt;a href="https://www.grc.com/passwords.htm"&gt;GRC's Perfect Password Page&lt;/a&gt;. Previously I had done the same thing while exploring Google App Script, and it seemed a good way to get my feed wet. So I am happy to announce a gadget that will not change the world but did teach me something about Google Gadgets: the &lt;a href="http://www.google.com/ig/directory?url=gadgets.he3.com.au/grcpassword/gadget.xml"&gt;GRC Passwords Gadget&lt;/a&gt;.&lt;br /&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://4.bp.blogspot.com/_qLhNkiMU1-k/S_0PL-FNbSI/AAAAAAAAy6U/bACaLNQ-Dew/s1600/GRC+Passwords+-+Add+to+your+homepage+26052010+100757+PM.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="250" src="http://4.bp.blogspot.com/_qLhNkiMU1-k/S_0PL-FNbSI/AAAAAAAAy6U/bACaLNQ-Dew/s400/GRC+Passwords+-+Add+to+your+homepage+26052010+100757+PM.jpg" width="400" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;
&lt;a name='more'&gt;&lt;/a&gt;This gadget does a very simple job. It connects to the Perfect Passwords page, retrieves the contents and parses &amp;nbsp;out the requested password type (hex, printable and alphanumeric) and length. Nothing too tricky.&lt;br /&gt;
&lt;br /&gt;
What is more interesting is the usefulness of Google Gadgets in general. Gadgets do not seem to get a great deal of attention from Google these days but many of their products support them. You can write one gadget which can be added to &lt;a href="http://www.google.com/ig"&gt;iGoogle&lt;/a&gt;, &lt;a href="http://code.google.com/hosting/"&gt;Project hosting&lt;/a&gt; on Google Code, &lt;a href="https://sites.google.com/"&gt;Google Sites&lt;/a&gt;, &lt;a href="http://gmail.com/"&gt;Gmail&lt;/a&gt;, &lt;a href="http://calendar.google.com/"&gt;Google Calendar&lt;/a&gt;, &lt;a href="http://maps.google.com/"&gt;Google Maps&lt;/a&gt; (ie. 'Maplets')&amp;nbsp;and any other container which supports Gadgets (such as &lt;a href="http://shindig.apache.org/"&gt;Apache Shindig&lt;/a&gt;). My GRC password gadget may never change the world but quick access to it within Gmail makes it very handy.&lt;br /&gt;
&lt;br /&gt;
For companies trying to reach customers deployed on Google Apps through the &lt;a href="http://www.google.com/enterprise/marketplace/"&gt;Google Apps Marketplace&lt;/a&gt;, gadgets are an excellent opportunity to escape the silo of your application and integration your service more fully with the rest of the the Google Apps offerings. Of particular interest is the only real development in Google Gadgets that I have noticed in recent history: &lt;a href="http://code.google.com/apis/gmail/gadgets/contextual/"&gt;Gmail Contextual Gadgets&lt;/a&gt;. These gadgets can appear inline in email messages based on the content of the email itself.&lt;br /&gt;
&lt;br /&gt;
For the moment I am hanging up my Gadget developer coat, but I have a feeling I might be back for when I have an application which needs good integration with the Google world - or when I get a decent idea to implement :)&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9174832840890796616-5334235490245032649?l=www.learningtechnicalstuff.com' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/LearningTechnicalStuff/~4/QvkSEOwoV1k" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://www.learningtechnicalstuff.com/feeds/5334235490245032649/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.learningtechnicalstuff.com/2010/06/grc-password-google-gadget.html#comment-form" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/9174832840890796616/posts/default/5334235490245032649?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/9174832840890796616/posts/default/5334235490245032649?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/LearningTechnicalStuff/~3/QvkSEOwoV1k/grc-password-google-gadget.html" title="GRC Password Google Gadget" /><author><name>Ben Davies</name><uri>http://www.blogger.com/profile/15225977828752157616</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="29" height="32" src="http://4.bp.blogspot.com/_qLhNkiMU1-k/Sh_l4FTCGkI/AAAAAAAAAEY/ABoYTtyFXEA/S220/IMG_2414.jpg" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://4.bp.blogspot.com/_qLhNkiMU1-k/S_0PL-FNbSI/AAAAAAAAy6U/bACaLNQ-Dew/s72-c/GRC+Passwords+-+Add+to+your+homepage+26052010+100757+PM.jpg" height="72" width="72" /><thr:total>0</thr:total><feedburner:origLink>http://www.learningtechnicalstuff.com/2010/06/grc-password-google-gadget.html</feedburner:origLink></entry><entry gd:etag="W/&quot;AkcDRnY6cCp7ImA9WxFWGEw.&quot;"><id>tag:blogger.com,1999:blog-9174832840890796616.post-7986045332780207370</id><published>2010-06-06T02:52:00.000-07:00</published><updated>2010-06-06T02:54:37.818-07:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2010-06-06T02:54:37.818-07:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="Google App Engine" /><category scheme="http://www.blogger.com/atom/ns#" term="he3-appengine-lib" /><title>PageLinks - a simple helper class for PagedQuery</title><content type="html">On May 26th I added &lt;a href="http://code.google.com/p/he3-appengine-lib/wiki/PageLinks"&gt;PageLinks&lt;/a&gt;&amp;nbsp;to the already published &lt;a href="http://code.google.com/p/he3-appengine-lib/wiki/PagedQuery"&gt;PagedQuery&lt;/a&gt; class available on &lt;a href="http://code.google.com/p/he3-appengine-lib/"&gt;he3-appengine-lib&lt;/a&gt;. PagedQuery is a python class to add a paging abstraction to the vanilla db.Query and db.GqlQuery used on Google App Engine (announced &lt;a href="http://www.learningtechnicalstuff.com/2010/04/pagedquery-easy-paging-using-cursors-on.html"&gt;here&lt;/a&gt;). PageLinks is a simple helper class that generates a sequence of page number / URL pairs for use in page navigation, including &lt;i&gt;previous &lt;/i&gt;and &lt;i&gt;next&lt;/i&gt; links.&lt;br /&gt;
&lt;br /&gt;
&lt;a name='more'&gt;&lt;/a&gt;&lt;br /&gt;
Usage of PageLinks is straight forward. Create and initialise an instance of the class and then call the get_links() method:&lt;br /&gt;
&lt;br /&gt;
&lt;code&gt;&lt;br /&gt;
pageLinks = he3.db.tower.paging.PageLinks( page=2,&lt;br /&gt;
&amp;nbsp;&amp;nbsp; page_count=3, &amp;nbsp;url_root='http://myresources/', page_field='page')&lt;br /&gt;
Sequence_of_links = pageLinks.get_links()&lt;br /&gt;
&lt;/code&gt;&lt;br /&gt;
&lt;br /&gt;
Sequence_of_links now equals:&lt;br /&gt;
&lt;br /&gt;
&lt;code&gt;&lt;br /&gt;
[('prev', 'http://myresources/?page=1'),&lt;br /&gt;
('1', 'http://myresources/?page=1'),&lt;br /&gt;
('2', 'http://myresources/?page=1'),&lt;br /&gt;
('3', 'http://myresources/?page=2'),&lt;br /&gt;
('next',&amp;nbsp;&amp;nbsp;'http://myresources/?page=3')]&lt;br /&gt;
&lt;/code&gt;&lt;br /&gt;
&lt;br /&gt;
This is simple helper class. I think I am most proud of the fact I finally made good use of a Python List Comprehension.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9174832840890796616-7986045332780207370?l=www.learningtechnicalstuff.com' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/LearningTechnicalStuff/~4/M0UY_TtDR9c" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://www.learningtechnicalstuff.com/feeds/7986045332780207370/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.learningtechnicalstuff.com/2010/06/pagelinks-simple-helper-class-for.html#comment-form" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/9174832840890796616/posts/default/7986045332780207370?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/9174832840890796616/posts/default/7986045332780207370?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/LearningTechnicalStuff/~3/M0UY_TtDR9c/pagelinks-simple-helper-class-for.html" title="PageLinks - a simple helper class for PagedQuery" /><author><name>Ben Davies</name><uri>http://www.blogger.com/profile/15225977828752157616</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="29" height="32" src="http://4.bp.blogspot.com/_qLhNkiMU1-k/Sh_l4FTCGkI/AAAAAAAAAEY/ABoYTtyFXEA/S220/IMG_2414.jpg" /></author><thr:total>0</thr:total><feedburner:origLink>http://www.learningtechnicalstuff.com/2010/06/pagelinks-simple-helper-class-for.html</feedburner:origLink></entry><entry gd:etag="W/&quot;C0QFQnszcCp7ImA9WxFWEkw.&quot;"><id>tag:blogger.com,1999:blog-9174832840890796616.post-511546290625902325</id><published>2010-05-30T02:21:00.000-07:00</published><updated>2010-05-30T02:21:53.588-07:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2010-05-30T02:21:53.588-07:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="Google App Script" /><title>Google App Script: Now with Time-based Triggers.</title><content type="html">&lt;a href="http://www.google.com/google-d-s/scripts/overview.html"&gt;Google App Script&lt;/a&gt; is continuing to develop. You can interact with not only the spreadsheet that hosts the script but also email, Google Sites, Calendars, Forms, Documents and the DocList, Web pages and services, XML, Maps and Contacts. Check out the &lt;a href="http://www.google.com/google-d-s/scripts/overview.html"&gt;documentation&lt;/a&gt;. What I was not aware of is that you can now incorporate &lt;a href="http://www.google.com/google-d-s/scripts/guide.html#TimeTriggers"&gt;time-based triggers&lt;/a&gt; into your scripts, which makes them a lot more useful. I have resolved today to publish a simple site about my upcoming wedding to receive RSVPs and publish wedding information - no doubt using Sites and Google App Script.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9174832840890796616-511546290625902325?l=www.learningtechnicalstuff.com' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/LearningTechnicalStuff/~4/sjDkOYSh9Gk" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://www.learningtechnicalstuff.com/feeds/511546290625902325/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.learningtechnicalstuff.com/2010/05/google-app-script-now-with-time-based.html#comment-form" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/9174832840890796616/posts/default/511546290625902325?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/9174832840890796616/posts/default/511546290625902325?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/LearningTechnicalStuff/~3/sjDkOYSh9Gk/google-app-script-now-with-time-based.html" title="Google App Script: Now with Time-based Triggers." /><author><name>Ben Davies</name><uri>http://www.blogger.com/profile/15225977828752157616</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="29" height="32" src="http://4.bp.blogspot.com/_qLhNkiMU1-k/Sh_l4FTCGkI/AAAAAAAAAEY/ABoYTtyFXEA/S220/IMG_2414.jpg" /></author><thr:total>0</thr:total><feedburner:origLink>http://www.learningtechnicalstuff.com/2010/05/google-app-script-now-with-time-based.html</feedburner:origLink></entry></feed>

