<?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;D08NRHo6fSp7ImA9WhRRFE4.&quot;"><id>tag:blogger.com,1999:blog-5502193732299119611</id><updated>2011-11-27T15:18:15.415-08:00</updated><category term="Storytime" /><category term="UsingHooks" /><category term="JOGRE" /><category term="Opinion" /><category term="SQLite" /><category term="External Resources" /><category term="Java" /><category term="python4nonpro" /><category term="Startup" /><category term="patterns-of-doom" /><category term="Testing" /><category term="Schluesselmeister" /><title>App Engine Fan</title><subtitle type="html">Google Web Toolkit, App Engine, and other cloud based programming goodness :-)</subtitle><link rel="http://schemas.google.com/g/2005#feed" type="application/atom+xml" href="http://blog.appenginefan.com/feeds/posts/default" /><link rel="alternate" type="text/html" href="http://blog.appenginefan.com/" /><link rel="next" type="application/atom+xml" href="http://www.blogger.com/feeds/5502193732299119611/posts/default?start-index=11&amp;max-results=10&amp;redirect=false&amp;v=2" /><author><name>The App Engine Fan</name><uri>http://www.blogger.com/profile/14769199738243128956</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="http://3.bp.blogspot.com/_ZXENRUvBme8/Sd1VsOCz0yI/AAAAAAAAAS4/rhRM7bx1PqI/S220/avatar.jpg" /></author><generator version="7.00" uri="http://www.blogger.com">Blogger</generator><openSearch:totalResults>102</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>10</openSearch:itemsPerPage><atom10:link xmlns:atom10="http://www.w3.org/2005/Atom" rel="self" type="application/atom+xml" href="http://feeds.feedburner.com/AppEngineFan" /><feedburner:info uri="appenginefan" /><atom10:link xmlns:atom10="http://www.w3.org/2005/Atom" rel="hub" href="http://pubsubhubbub.appspot.com/" /><entry gd:etag="W/&quot;A0EMSHYzeSp7ImA9WhdSE0g.&quot;"><id>tag:blogger.com,1999:blog-5502193732299119611.post-2149175035913976236</id><published>2011-07-22T11:31:00.000-07:00</published><updated>2011-07-22T11:34:49.881-07:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-07-22T11:34:49.881-07:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="python4nonpro" /><title>Python for non programmers, part 4</title><content type="html">During the last lesson, we learned how to filter out rows with invalid input. However, it is not always desirable to delete an entire row. The following variation will intercept problems on a per-value basis instead and append the word &lt;i&gt;Invalid&lt;/i&gt; instead:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;# Import libraries                                                                                                        &lt;br /&gt;import csv&lt;br /&gt;import operator&lt;br /&gt;&lt;br /&gt;# Open files and csv readers/writers                                                                                      &lt;br /&gt;infile = open('data.txt', 'rb')&lt;br /&gt;in_csv = csv.reader(infile, delimiter=',')&lt;br /&gt;outfile = open('data2.txt', 'wb')&lt;br /&gt;out_csv = csv.writer(outfile, delimiter=',')&lt;br /&gt;&lt;br /&gt;# Process values from a row and intercept errors                                                                          &lt;br /&gt;def process(row, indices, converter, function):&lt;br /&gt;  try:&lt;br /&gt;    values = [converter(row[i]) for i in indices]&lt;br /&gt;    return reduce(function, values)&lt;br /&gt;  except(ValueError, IndexError):&lt;br /&gt;    return 'Invalid'&lt;br /&gt;&lt;br /&gt;# Go through the rows                                                                                                     &lt;br /&gt;is_header = True&lt;br /&gt;for row in in_csv:&lt;br /&gt;  if is_header:&lt;br /&gt;    row = row + ['Sum', 'Max', 'Description']&lt;br /&gt;    is_header = False&lt;br /&gt;  else:&lt;br /&gt;    row.append(process(row, [1,2], float, operator.add))&lt;br /&gt;    row.append(process(row, [1,2], int, max))&lt;br /&gt;    row.append(process(row, [1,2], str,&lt;br /&gt;        lambda x,y: 'Value pair of %s and %s' % (x, y)))&lt;br /&gt;  out_csv.writerow(row)&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Your mission, should you choose to accept:&lt;br /&gt;&lt;br /&gt;&lt;ul&gt;&lt;br /&gt;&lt;li&gt;Try out the program. Is the output as you would have expected?&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Add two more columns to each row. The first one should show the difference between the first and second column (a-b), the second one should show the maximum of &lt;code&gt;a/10&lt;/code&gt; and &lt;code&gt;b/11=5&lt;/code&gt;.&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Modify the program to filter out any incoming row that has fewer than 3 columns. Hint: use &lt;code&gt;len(row)&lt;/code&gt; to get to the amount of columns.&lt;br /&gt;&lt;/ul&gt;&lt;div class="blogger-post-footer"&gt;&lt;script language="javascript" src="http://tipjoy.com/custombutton?targetUser=schefflerjens" scrolling="no" frameborder="0" marginwidth="0" marginheight="0" hspace="0" vspace="0" allowtransparency="true" &gt;&lt;/script&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5502193732299119611-2149175035913976236?l=blog.appenginefan.com' alt='' /&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/l5ZFpyFxNOqdmyYhYeL_VSZAElc/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/l5ZFpyFxNOqdmyYhYeL_VSZAElc/0/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;br/&gt;
&lt;a href="http://feedads.g.doubleclick.net/~a/l5ZFpyFxNOqdmyYhYeL_VSZAElc/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/l5ZFpyFxNOqdmyYhYeL_VSZAElc/1/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/AppEngineFan?a=LvQeFkRxv3A:2D93dOgXrJ0:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/AppEngineFan?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/AppEngineFan?a=LvQeFkRxv3A:2D93dOgXrJ0:4cEx4HpKnUU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/AppEngineFan?i=LvQeFkRxv3A:2D93dOgXrJ0:4cEx4HpKnUU" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/AppEngineFan?a=LvQeFkRxv3A:2D93dOgXrJ0:gIN9vFwOqvQ"&gt;&lt;img src="http://feeds.feedburner.com/~ff/AppEngineFan?i=LvQeFkRxv3A:2D93dOgXrJ0:gIN9vFwOqvQ" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://blog.appenginefan.com/feeds/2149175035913976236/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=5502193732299119611&amp;postID=2149175035913976236" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/5502193732299119611/posts/default/2149175035913976236?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/5502193732299119611/posts/default/2149175035913976236?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/AppEngineFan/~3/LvQeFkRxv3A/python-for-non-programmers-part-4.html" title="Python for non programmers, part 4" /><author><name>The App Engine Fan</name><uri>http://www.blogger.com/profile/14769199738243128956</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="http://3.bp.blogspot.com/_ZXENRUvBme8/Sd1VsOCz0yI/AAAAAAAAAS4/rhRM7bx1PqI/S220/avatar.jpg" /></author><thr:total>0</thr:total><feedburner:origLink>http://blog.appenginefan.com/2011/07/python-for-non-programmers-part-4.html</feedburner:origLink></entry><entry gd:etag="W/&quot;C0MDRHo4cSp7ImA9WhdSEU8.&quot;"><id>tag:blogger.com,1999:blog-5502193732299119611.post-8213659783345442350</id><published>2011-07-19T17:16:00.000-07:00</published><updated>2011-07-19T17:24:35.439-07:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-07-19T17:24:35.439-07:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="python4nonpro" /><title>Python for non programmers, part 3</title><content type="html">Today, we will look at data format errors. Replace your old input file with the following file and re-run your program:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;Row A,Row B,Row C&lt;br /&gt;Not a number,a,2&lt;br /&gt;Missing a value,1&lt;br /&gt;&lt;br /&gt;After newline,3,4&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;The first time you run the program, you will see an error message:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;Traceback (most recent call last):&lt;br /&gt;  File "csvcopy.py", line 17, in &lt;module&gt;&lt;br /&gt;    row.append(float(row[1]) + float(row[2]))&lt;br /&gt;ValueError: invalid literal for float(): a&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;If you deleted the line that causes the issue and reran the program, you would see&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;Traceback (most recent call last):&lt;br /&gt;  File "csvcopy.py", line 17, in &lt;module&gt;&lt;br /&gt;    row.append(float(row[1]) + float(row[2]))&lt;br /&gt;IndexError: list index out of range&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Would you also delete that line, you would get the same error also for the blank line in your file. How do we fix this?&lt;br /&gt;&lt;br /&gt;Replace your current program without the following code:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;# Import libraries                                                                                           &lt;br /&gt;import csv&lt;br /&gt;&lt;br /&gt;# Open files and csv readers/writers                                                                         &lt;br /&gt;infile = open('data.txt', 'rb')&lt;br /&gt;in_csv = csv.reader(infile, delimiter=',')&lt;br /&gt;outfile = open('data2.txt', 'wb')&lt;br /&gt;out_csv = csv.writer(outfile, delimiter=',')&lt;br /&gt;&lt;br /&gt;# Go through the rows                                                                                        &lt;br /&gt;is_header = True&lt;br /&gt;row_number = 0&lt;br /&gt;for row in in_csv:&lt;br /&gt;  row_number = row_number + 1&lt;br /&gt;  is_valid = True&lt;br /&gt;  if is_header:&lt;br /&gt;    row = row + ['Sum', 'Max', 'Description']&lt;br /&gt;    is_header = False&lt;br /&gt;  else:&lt;br /&gt;    try:&lt;br /&gt;      row.append(float(row[1]) + float(row[2]))&lt;br /&gt;      row.append(max(int(row[1]), int(row[2])))&lt;br /&gt;      row.append('Value pair of %s and %s' % (row[1], row[2]))&lt;br /&gt;    except(ValueError, IndexError):&lt;br /&gt;      print 'Row %s is invalid, skipping...' % row_number&lt;br /&gt;      is_valid = False&lt;br /&gt;  if is_valid:&lt;br /&gt;    out_csv.writerow(row)&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;This code will use a try/except statement to intercept any errors in parsing numbers (ValueError) or rows with insufficient amount of columns (IndexError) and not write them into the file. Your mission, should you choose toy accept: extend the program to collect all these incorrect lines in a second output-file called "invalid.txt". Try doing so by adding 6 lines or fewer.&lt;div class="blogger-post-footer"&gt;&lt;script language="javascript" src="http://tipjoy.com/custombutton?targetUser=schefflerjens" scrolling="no" frameborder="0" marginwidth="0" marginheight="0" hspace="0" vspace="0" allowtransparency="true" &gt;&lt;/script&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5502193732299119611-8213659783345442350?l=blog.appenginefan.com' alt='' /&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/usp2GNqwdLhYZ973JlRke-KgvUI/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/usp2GNqwdLhYZ973JlRke-KgvUI/0/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;br/&gt;
&lt;a href="http://feedads.g.doubleclick.net/~a/usp2GNqwdLhYZ973JlRke-KgvUI/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/usp2GNqwdLhYZ973JlRke-KgvUI/1/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/AppEngineFan?a=HUO8K-YPdLc:xvM1Hast-YY:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/AppEngineFan?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/AppEngineFan?a=HUO8K-YPdLc:xvM1Hast-YY:4cEx4HpKnUU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/AppEngineFan?i=HUO8K-YPdLc:xvM1Hast-YY:4cEx4HpKnUU" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/AppEngineFan?a=HUO8K-YPdLc:xvM1Hast-YY:gIN9vFwOqvQ"&gt;&lt;img src="http://feeds.feedburner.com/~ff/AppEngineFan?i=HUO8K-YPdLc:xvM1Hast-YY:gIN9vFwOqvQ" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://blog.appenginefan.com/feeds/8213659783345442350/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=5502193732299119611&amp;postID=8213659783345442350" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/5502193732299119611/posts/default/8213659783345442350?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/5502193732299119611/posts/default/8213659783345442350?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/AppEngineFan/~3/HUO8K-YPdLc/python-for-non-programmers-part-3.html" title="Python for non programmers, part 3" /><author><name>The App Engine Fan</name><uri>http://www.blogger.com/profile/14769199738243128956</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="http://3.bp.blogspot.com/_ZXENRUvBme8/Sd1VsOCz0yI/AAAAAAAAAS4/rhRM7bx1PqI/S220/avatar.jpg" /></author><thr:total>0</thr:total><feedburner:origLink>http://blog.appenginefan.com/2011/07/python-for-non-programmers-part-3.html</feedburner:origLink></entry><entry gd:etag="W/&quot;C0UCQHw-eyp7ImA9WhdSEU8.&quot;"><id>tag:blogger.com,1999:blog-5502193732299119611.post-9099555827761942180</id><published>2011-07-14T21:06:00.000-07:00</published><updated>2011-07-19T17:21:01.253-07:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-07-19T17:21:01.253-07:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="python4nonpro" /><title>Python for non programmers, part 2</title><content type="html">&lt;p&gt;&lt;br /&gt;Welcome back, grasshopper. Ready for another round?&lt;br /&gt;&lt;/p&gt;&lt;br /&gt;&lt;p&gt;&lt;br /&gt;&lt;i&gt;Note:&lt;/i&gt;Before you begin, assumed you use Notepad++, go to Settings/Preferences and navigate to the &amp;quot;Language menu and tab settings&amp;quot; tab. Change the &amp;quot;Tab size&amp;quot; from 4 to 2, and make sure the &amp;quot;replace by space&amp;quot; option is selected.&lt;br /&gt;&lt;/p&gt;&lt;br /&gt;&lt;p&gt;&lt;br /&gt;&lt;b&gt;Lesson 2: Simple data manipulation.&lt;/b&gt;&lt;br /&gt;&lt;/p&gt;&lt;br /&gt;&lt;p&gt;&lt;br /&gt;Today, we are going to take our original csv data file and add a couple of rows programatically. Our program will&lt;br /&gt;&lt;ul&gt;&lt;br /&gt;&lt;li&gt;Extend the table header (first row) with three new labels (&amp;quot;Sum&amp;quot;, &amp;quot;Max&amp;quot;, and &amp;quot;Description&amp;quot;).&lt;/li&gt;&lt;br /&gt;&lt;li&gt;For each data row, compute the sum as a floating point number (like &lt;code&gt;3.0&lt;/code&gt;).&lt;/li&gt;&lt;br /&gt;&lt;li&gt;For each data row, compute the maximum as an integer point number (like &lt;code&gt;3&lt;/code&gt;).&lt;/li&gt;&lt;br /&gt;&lt;li&gt;For each data row, add a quick textual description that contains both numbers.&lt;/li&gt;&lt;br /&gt;&lt;/ul&gt;&lt;br /&gt;&lt;/p&gt;&lt;br /&gt;&lt;p&gt;&lt;br /&gt;Open up a program file, e.g. &lt;code&gt;csvcopy.py&lt;/code&gt; in your editor and create the following program (remember, leading whitespaces are important in python):&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;# Import libraries&lt;br /&gt;import csv&lt;br /&gt;&lt;br /&gt;# Open files and csv readers/writers&lt;br /&gt;infile = open('data.txt', 'rb')&lt;br /&gt;in_csv = csv.reader(infile, delimiter=',')&lt;br /&gt;outfile = open('data2.txt', 'wb')&lt;br /&gt;out_csv = csv.writer(outfile, delimiter=',')&lt;br /&gt;&lt;br /&gt;# Go through the rows&lt;br /&gt;is_header = True&lt;br /&gt;for row in in_csv:&lt;br /&gt;  if is_header:&lt;br /&gt;    row = row + ['Sum', 'Max', 'Description']&lt;br /&gt;    is_header = False&lt;br /&gt;  else:&lt;br /&gt;    row.append(float(row[1]) + float(row[2]))&lt;br /&gt;    row.append(max(int(row[1]), int(row[2])))&lt;br /&gt;    row.append('Value pair of %s and %s' % (row[1], row[2]))    &lt;br /&gt;  out_csv.writerow(row)&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;/p&gt;&lt;br /&gt;&lt;br /&gt;&lt;p&gt;&lt;br /&gt;Just like last time, run the program and look at the output. It should look something like this:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;Row A,Row B,Row C,Sum,Max,Description&lt;br /&gt;Hello,1,2,3.0,2,Value pair of 1 and 2&lt;br /&gt;World,3,4,7.0,4,Value pair of 3 and 4&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;/p&gt;&lt;br /&gt;&lt;br /&gt;&lt;p&gt;&lt;br /&gt;Experiment with the program to do other data manipulation. Try to answer the following questions from looking at the file:&lt;br /&gt;&lt;ul&gt;&lt;br /&gt;&lt;li&gt;How do I access a single element in a list of values such as &lt;code&gt;row&lt;/code&gt;&lt;/li&gt;&lt;br /&gt;&lt;li&gt;How do I create a comment?&lt;/li&gt;&lt;br /&gt;&lt;li&gt;How do I add two values?&lt;/li&gt;&lt;br /&gt;&lt;li&gt;How do I find the maximum of two values?&lt;/li&gt;&lt;br /&gt;&lt;li&gt;How do I create a floating point number from a text?&lt;/li&gt;&lt;br /&gt;&lt;li&gt;How do I create an integer number from a text?&lt;/li&gt;&lt;br /&gt;&lt;li&gt;How do I append a single value to a list (row)?&lt;/li&gt;&lt;br /&gt;&lt;li&gt;How do I append multiple values to a list (row) at once?&lt;/li&gt;&lt;br /&gt;&lt;/ul&gt;&lt;br /&gt;&lt;br /&gt;For bonus points, try to understand how the last &lt;code&gt;row.append&lt;/code&gt; in the program works.&lt;br /&gt;You can find more information about that &lt;a href="http://diveintopython.org/native_data_types/formatting_strings.html"&gt;here&lt;/a&gt;.&lt;br /&gt;&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;script language="javascript" src="http://tipjoy.com/custombutton?targetUser=schefflerjens" scrolling="no" frameborder="0" marginwidth="0" marginheight="0" hspace="0" vspace="0" allowtransparency="true" &gt;&lt;/script&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5502193732299119611-9099555827761942180?l=blog.appenginefan.com' alt='' /&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/7QxvOQE6GrAI7obJukWKWPsFBXA/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/7QxvOQE6GrAI7obJukWKWPsFBXA/0/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;br/&gt;
&lt;a href="http://feedads.g.doubleclick.net/~a/7QxvOQE6GrAI7obJukWKWPsFBXA/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/7QxvOQE6GrAI7obJukWKWPsFBXA/1/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/AppEngineFan?a=AmYUDYiMn1I:uyswIq5-1H8:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/AppEngineFan?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/AppEngineFan?a=AmYUDYiMn1I:uyswIq5-1H8:4cEx4HpKnUU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/AppEngineFan?i=AmYUDYiMn1I:uyswIq5-1H8:4cEx4HpKnUU" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/AppEngineFan?a=AmYUDYiMn1I:uyswIq5-1H8:gIN9vFwOqvQ"&gt;&lt;img src="http://feeds.feedburner.com/~ff/AppEngineFan?i=AmYUDYiMn1I:uyswIq5-1H8:gIN9vFwOqvQ" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://blog.appenginefan.com/feeds/9099555827761942180/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=5502193732299119611&amp;postID=9099555827761942180" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/5502193732299119611/posts/default/9099555827761942180?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/5502193732299119611/posts/default/9099555827761942180?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/AppEngineFan/~3/AmYUDYiMn1I/python-for-non-programmers-part-2.html" title="Python for non programmers, part 2" /><author><name>The App Engine Fan</name><uri>http://www.blogger.com/profile/14769199738243128956</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="http://3.bp.blogspot.com/_ZXENRUvBme8/Sd1VsOCz0yI/AAAAAAAAAS4/rhRM7bx1PqI/S220/avatar.jpg" /></author><thr:total>0</thr:total><feedburner:origLink>http://blog.appenginefan.com/2011/07/python-for-non-programmers-part-2.html</feedburner:origLink></entry><entry gd:etag="W/&quot;C0UCQHw-eyp7ImA9WhdSEU8.&quot;"><id>tag:blogger.com,1999:blog-5502193732299119611.post-5244083776218197160</id><published>2011-07-12T18:40:00.000-07:00</published><updated>2011-07-19T17:21:01.253-07:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-07-19T17:21:01.253-07:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="python4nonpro" /><title>Python for non programmers, part 1</title><content type="html">A good friend of mine (not an engineer) recently faced a problem. Customers sent in data samples (usually as comma-separated files) that needed to be processed and/or validated for conformance to a specification. My friend spent hours importing the files into spreadsheets and going through them manually. The data was large for a human (a few thousand rows), but small for a computer. My friend had some basic programming skills from college and early jobs, but over the years, those had become a bit rusty. I wondered if I could help, so here goes...&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;The objective:&lt;/div&gt;&lt;div&gt;&lt;ul&gt;&lt;li&gt;Getting my friend all the information needed with as little theory as possible&lt;/li&gt;&lt;li&gt;Focus on the specific use case&lt;/li&gt;&lt;li&gt;Keep it simple (no list comprehensions, or any of that stuff)&lt;/li&gt;&lt;li&gt;Must work on windows machines with minimal system modifcations.&lt;/li&gt;&lt;/ul&gt;&lt;div&gt;The final result will hopefully be a chain of simple exercises to get this person up to speed.&lt;/div&gt;&lt;br /&gt;&lt;div&gt;&lt;b&gt;&lt;span class="Apple-style-span"&gt;Lesson 1: Install python, read and write a csv file:&lt;/span&gt;&lt;/b&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;ul&gt;&lt;li&gt;Install python, version 2, from &lt;a href="http://python.org/download/"&gt;http://python.org/download/&lt;/a&gt;. Currently, the latest installer can be found &lt;a href="http://python.org/ftp/python/2.7.2/python-2.7.2.msi"&gt;here&lt;/a&gt;. Remember the folder of where python was installed.&lt;/li&gt;&lt;li&gt;Optionally, install an editor like &lt;a href="http://notepad-plus-plus.org/"&gt;Notepad++&lt;/a&gt; (otherwise, use regular notepad)&lt;/li&gt;&lt;li&gt;Create a folder, for example &lt;span class="Apple-style-span"&gt;c:\example&lt;/span&gt;&lt;/li&gt;&lt;li&gt;Add a file called &lt;span class="Apple-style-span"&gt;data.txt&lt;/span&gt; with the following three lines of content:&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div&gt;&lt;pre&gt;Row A,Row B,Row C&lt;br /&gt;Hello,1,2&lt;br /&gt;World,3,4&lt;/pre&gt;&lt;ul&gt;&lt;li&gt;Add a file called &lt;span class="Apple-style-span"&gt;csvcopy.py&lt;/span&gt; with the following content (the leading whitespace in the last line is important):&lt;/li&gt;&lt;/ul&gt;&lt;pre&gt;import csv&lt;br /&gt;infile = open('data.txt', 'rb')&lt;br /&gt;in_csv = csv.reader(infile, delimiter=',')&lt;br /&gt;outfile = open('data2.txt', 'wb')&lt;br /&gt;out_csv = csv.writer(outfile, delimiter=',')&lt;br /&gt;for row in in_csv:&lt;br /&gt;  out_csv.writerow(row)&lt;/pre&gt;&lt;ul&gt;&lt;li&gt;Open a command prompt and switch to the folder that contains &lt;span class="Apple-style-span"&gt;data.txt&lt;/span&gt; and &lt;span class="Apple-style-span"&gt;csvcopy.py&lt;/span&gt;.&lt;/li&gt;&lt;li&gt;Run "&lt;span class="Apple-style-span"&gt;C:\Python\python.exe csvcopy.py&lt;/span&gt;" (path needs to be adjusted depending on where you installed python).&lt;/li&gt;&lt;li&gt;Open the newly created file "&lt;span class="Apple-style-span"&gt;data2.txt&lt;/span&gt;" and look at its content.&lt;/li&gt;&lt;li&gt;Exercise: rename &lt;span class="Apple-style-span"&gt;data.txt&lt;/span&gt; to &lt;span class="Apple-style-span"&gt;data.csv&lt;/span&gt; and modify the program to write the result into &lt;span class="Apple-style-span"&gt;data2.csv&lt;/span&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;script language="javascript" src="http://tipjoy.com/custombutton?targetUser=schefflerjens" scrolling="no" frameborder="0" marginwidth="0" marginheight="0" hspace="0" vspace="0" allowtransparency="true" &gt;&lt;/script&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5502193732299119611-5244083776218197160?l=blog.appenginefan.com' alt='' /&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/6xjqO-AqZ_DGQ_cWGvAKjj-xmdw/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/6xjqO-AqZ_DGQ_cWGvAKjj-xmdw/0/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;br/&gt;
&lt;a href="http://feedads.g.doubleclick.net/~a/6xjqO-AqZ_DGQ_cWGvAKjj-xmdw/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/6xjqO-AqZ_DGQ_cWGvAKjj-xmdw/1/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/AppEngineFan?a=b46dOum5z0s:7XYeTqC7JmQ:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/AppEngineFan?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/AppEngineFan?a=b46dOum5z0s:7XYeTqC7JmQ:4cEx4HpKnUU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/AppEngineFan?i=b46dOum5z0s:7XYeTqC7JmQ:4cEx4HpKnUU" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/AppEngineFan?a=b46dOum5z0s:7XYeTqC7JmQ:gIN9vFwOqvQ"&gt;&lt;img src="http://feeds.feedburner.com/~ff/AppEngineFan?i=b46dOum5z0s:7XYeTqC7JmQ:gIN9vFwOqvQ" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://blog.appenginefan.com/feeds/5244083776218197160/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=5502193732299119611&amp;postID=5244083776218197160" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/5502193732299119611/posts/default/5244083776218197160?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/5502193732299119611/posts/default/5244083776218197160?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/AppEngineFan/~3/b46dOum5z0s/python-for-non-programmers-part-1.html" title="Python for non programmers, part 1" /><author><name>The App Engine Fan</name><uri>http://www.blogger.com/profile/14769199738243128956</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="http://3.bp.blogspot.com/_ZXENRUvBme8/Sd1VsOCz0yI/AAAAAAAAAS4/rhRM7bx1PqI/S220/avatar.jpg" /></author><thr:total>0</thr:total><feedburner:origLink>http://blog.appenginefan.com/2011/07/python-for-non-programmers-part-1.html</feedburner:origLink></entry><entry gd:etag="W/&quot;C08BSXc6cCp7ImA9WxFREk8.&quot;"><id>tag:blogger.com,1999:blog-5502193732299119611.post-7185228247359472603</id><published>2010-04-25T11:32:00.000-07:00</published><updated>2010-04-25T11:57:38.918-07:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2010-04-25T11:57:38.918-07:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="patterns-of-doom" /><title>Patterns of doom: chain of fetches</title><content type="html">Let's look at another commonly seen &lt;a href="http://blog.appenginefan.com/2010/04/patterns-of-doom.html"&gt;pattern of doom&lt;/a&gt; that can be identified and eliminated using app stats. This specimen is very similar to &lt;a href="http://blog.appenginefan.com/2010/04/patterns-of-doom.html"&gt;chain of gets&lt;/a&gt;, but it is more commonly found in mashups that need to fetch a lot of data from the web. The situation is something like this:&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_ZXENRUvBme8/S9SPoSEOfiI/AAAAAAAAAXI/vbMY5x1pVJk/s1600/staircase_of_fetches.jpg"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 27px;" src="http://2.bp.blogspot.com/_ZXENRUvBme8/S9SPoSEOfiI/AAAAAAAAAXI/vbMY5x1pVJk/s320/staircase_of_fetches.jpg" border="0" alt=""id="BLOGGER_PHOTO_ID_5464150170114358818" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;The application gets from somewhere a list of data it needs to correlate. Each piece of data is somewhere on the web and needs to be fetched, e.g. from an atom feed or by scraping a html page. The developer then will get the data, e.g. like this:&lt;br /&gt;&lt;br /&gt;&lt;pre style="font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; color: #000000; background-color: #eee;font-size: 12px;border: 1px dashed #999999;line-height: 14px;padding: 5px; overflow: auto; width: 100%"&gt;&lt;code&gt;pages = [urlfetch.fetch(url) for url in urls]&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;and then do the operations necessary on the data to get the information. The problem is very similar to chain of gets: each request is slow, and performing them sequentially takes time.&lt;br /&gt;&lt;br /&gt;So, what is the solution to the problem? In the case of gets, we had an api that would let us perform all the datastore operations in one batch. For urlfetch, a similar effect can be achieved using &lt;a href="http://code.google.com/appengine/docs/python/urlfetch/asynchronousrequests.html"&gt;asynchronous requests&lt;/a&gt;:&lt;br /&gt;&lt;br /&gt;&lt;pre style="font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; color: #000000; background-color: #eee;font-size: 12px;border: 1px dashed #999999;line-height: 14px;padding: 5px; overflow: auto; width: 100%"&gt;&lt;code&gt;def asynchronous_get(list_of_urls):&lt;br /&gt;  rpcs = []&lt;br /&gt;  for url in list_of_urls:&lt;br /&gt;    rpc = urlfetch.create_rpc()&lt;br /&gt;    urlfetch.make_fetch_call(rpc, url)&lt;br /&gt;    rpcs.append(rpc)&lt;br /&gt;  return [rpc.get_result() for rpc in rpcs]&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;With this little helper method, we can parallelize the fetches in our previous example by simply making a small adjustment:&lt;br /&gt;&lt;br /&gt;&lt;pre style="font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; color: #000000; background-color: #eee;font-size: 12px;border: 1px dashed #999999;line-height: 14px;padding: 5px; overflow: auto; width: 100%"&gt;&lt;code&gt;pages = asynchronous_get(urls)&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_ZXENRUvBme8/S9SP0rgO2aI/AAAAAAAAAXQ/TBbXJVuYWpI/s1600/staircase_of_fetches_resolved.jpg"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 33px;" src="http://3.bp.blogspot.com/_ZXENRUvBme8/S9SP0rgO2aI/AAAAAAAAAXQ/TBbXJVuYWpI/s320/staircase_of_fetches_resolved.jpg" border="0" alt=""id="BLOGGER_PHOTO_ID_5464150383101139362" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;The improvement can be significant, as shown in the second screenshot. Note that app stats shows that the time spent in RPCs is about the same as in the original example, but because they all happened at the same time, the actual time spent (Grand total) is significantly less.&lt;br /&gt;&lt;br /&gt;As always, you can try out the pattern yourself at my &lt;a href="http://3.latest.googtst23.appspot.com/"&gt;demo site&lt;/a&gt;. Please don't overdo it though, since this is running on free quota and run out of it pretty soon otherwise. There is a reason I am calling those patterns of doom after all ;-)&lt;br /&gt;&lt;br /&gt;&lt;i&gt;PS: for those wondering why the first get in the screenshot is not batched: that's an independent call I make to find a list of youtube urls I'd like to fetch.&lt;/i&gt;&lt;div class="blogger-post-footer"&gt;&lt;script language="javascript" src="http://tipjoy.com/custombutton?targetUser=schefflerjens" scrolling="no" frameborder="0" marginwidth="0" marginheight="0" hspace="0" vspace="0" allowtransparency="true" &gt;&lt;/script&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5502193732299119611-7185228247359472603?l=blog.appenginefan.com' alt='' /&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/v2uXH4lXt72KWWw0Gz7jRQQVdE8/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/v2uXH4lXt72KWWw0Gz7jRQQVdE8/0/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;br/&gt;
&lt;a href="http://feedads.g.doubleclick.net/~a/v2uXH4lXt72KWWw0Gz7jRQQVdE8/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/v2uXH4lXt72KWWw0Gz7jRQQVdE8/1/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/AppEngineFan?a=G3hmmUi1eLI:5fBWIV8kya8:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/AppEngineFan?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/AppEngineFan?a=G3hmmUi1eLI:5fBWIV8kya8:4cEx4HpKnUU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/AppEngineFan?i=G3hmmUi1eLI:5fBWIV8kya8:4cEx4HpKnUU" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/AppEngineFan?a=G3hmmUi1eLI:5fBWIV8kya8:gIN9vFwOqvQ"&gt;&lt;img src="http://feeds.feedburner.com/~ff/AppEngineFan?i=G3hmmUi1eLI:5fBWIV8kya8:gIN9vFwOqvQ" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://blog.appenginefan.com/feeds/7185228247359472603/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=5502193732299119611&amp;postID=7185228247359472603" title="6 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/5502193732299119611/posts/default/7185228247359472603?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/5502193732299119611/posts/default/7185228247359472603?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/AppEngineFan/~3/G3hmmUi1eLI/patterns-of-doom-chain-of-fetches.html" title="Patterns of doom: chain of fetches" /><author><name>The App Engine Fan</name><uri>http://www.blogger.com/profile/14769199738243128956</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="http://3.bp.blogspot.com/_ZXENRUvBme8/Sd1VsOCz0yI/AAAAAAAAAS4/rhRM7bx1PqI/S220/avatar.jpg" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://2.bp.blogspot.com/_ZXENRUvBme8/S9SPoSEOfiI/AAAAAAAAAXI/vbMY5x1pVJk/s72-c/staircase_of_fetches.jpg" height="72" width="72" /><thr:total>6</thr:total><feedburner:origLink>http://blog.appenginefan.com/2010/04/patterns-of-doom-chain-of-fetches.html</feedburner:origLink></entry><entry gd:etag="W/&quot;CE4HQ3o6fSp7ImA9WxFSFkw.&quot;"><id>tag:blogger.com,1999:blog-5502193732299119611.post-199721722927005896</id><published>2010-04-18T10:38:00.000-07:00</published><updated>2010-04-18T10:48:52.415-07:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2010-04-18T10:48:52.415-07:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="patterns-of-doom" /><title>behind the scenes</title><content type="html">Some people may wonder how I got my "&lt;a href="http://3.latest.googtst23.appspot.com/"&gt;patterns of doom&lt;/a&gt;" app to automatically redirect to the app stats page after a request was executed. The secret is lies in applying a little hack to the app stats framework. On the &lt;a href="http://code.google.com/appengine/docs/python/tools/appstats.html#Installing_the_Event_Recorder"&gt;main documentation site&lt;/a&gt;, an &lt;code&gt;appengine_config.py&lt;/code&gt; is mentioned that installs the recording mechanism for app stats. I took that script and applied a small modification:&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span" style="font-family: arial, sans-serif; font-size: 13px; border-collapse: collapse; "&gt;&lt;pre style="white-space: pre-wrap; word-wrap: break-word; "&gt;&lt;span&gt;&lt;span class="Apple-style-span" style="font-size: medium;"&gt;   1: def webapp_add_wsgi_middleware(&lt;wbr&gt;app):&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span class="Apple-style-span" style="font-size: medium;"&gt;   2:     import os&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span class="Apple-style-span" style="font-size: medium;"&gt;   3:     from google.appengine.ext.appstats import recording&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span class="Apple-style-span" style="font-size: medium;"&gt;   4:     old_init = recording.Recorder.__init__&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span class="Apple-style-span" style="font-size: medium;"&gt;   5:     def new_init(self, env):&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span class="Apple-style-span" style="font-size: medium;"&gt;   6:       old_init(self, env)&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span class="Apple-style-span" style="font-size: medium;"&gt;   7:       link = 'http://%s%s/details?time=%s' % (&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span class="Apple-style-span" style="font-size: medium;"&gt;   8:           self.env.get('HTTP_HOST', ''),&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span class="Apple-style-span" style="font-size: medium;"&gt;   9:           '/stats',&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span class="Apple-style-span" style="font-size: medium;"&gt;  10:           int(self.start_timestamp * 1000))&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span class="Apple-style-span" style="font-size: medium;"&gt;  11:       &lt;span class="Apple-style-span"  style="color:#FF0000;"&gt;os.environ['STATS_LINK'] = link&lt;/span&gt;&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span class="Apple-style-span" style="font-size: medium;"&gt;  12:     recording.Recorder.__init__ = new_init&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span class="Apple-style-span" style="font-size: medium;"&gt;  13:     app = recording.appstats_wsgi_&lt;wbr&gt;middleware(app)&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span class="Apple-style-span" style="font-size: medium;"&gt;  14:     return app&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;pre style="white-space: pre-wrap; word-wrap: break-word; "&gt;&lt;span&gt;&lt;span style="font-size:130%;"&gt;&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;pre style="white-space: pre-wrap; word-wrap: break-word; "&gt;&lt;span&gt;&lt;span style="font-size:130%;"&gt;&lt;span class="Apple-style-span" style="border-collapse: separate; font-family: Georgia, serif; white-space: normal; font-size: 16px; "&gt;Basically, I am monkeypatching the constructor of the Recorder class to dump the link it will eventually generate into an environment variable. My request logic can then read that value and use it for redirects. Other applications would also be possible (like creating an asynchronous task that reads the value out and dumps it into another, more persistent state for longer-term analysis), so I thought I'd share the code with anyone interested :-)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/span&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;script language="javascript" src="http://tipjoy.com/custombutton?targetUser=schefflerjens" scrolling="no" frameborder="0" marginwidth="0" marginheight="0" hspace="0" vspace="0" allowtransparency="true" &gt;&lt;/script&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5502193732299119611-199721722927005896?l=blog.appenginefan.com' alt='' /&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/tE1z8BL6t4SNH3kbaKpxODWQt_Y/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/tE1z8BL6t4SNH3kbaKpxODWQt_Y/0/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;br/&gt;
&lt;a href="http://feedads.g.doubleclick.net/~a/tE1z8BL6t4SNH3kbaKpxODWQt_Y/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/tE1z8BL6t4SNH3kbaKpxODWQt_Y/1/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/AppEngineFan?a=-4U_5mLKZ1c:7G8zTpI4xqE:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/AppEngineFan?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/AppEngineFan?a=-4U_5mLKZ1c:7G8zTpI4xqE:4cEx4HpKnUU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/AppEngineFan?i=-4U_5mLKZ1c:7G8zTpI4xqE:4cEx4HpKnUU" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/AppEngineFan?a=-4U_5mLKZ1c:7G8zTpI4xqE:gIN9vFwOqvQ"&gt;&lt;img src="http://feeds.feedburner.com/~ff/AppEngineFan?i=-4U_5mLKZ1c:7G8zTpI4xqE:gIN9vFwOqvQ" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://blog.appenginefan.com/feeds/199721722927005896/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=5502193732299119611&amp;postID=199721722927005896" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/5502193732299119611/posts/default/199721722927005896?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/5502193732299119611/posts/default/199721722927005896?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/AppEngineFan/~3/-4U_5mLKZ1c/behind-scenes.html" title="behind the scenes" /><author><name>The App Engine Fan</name><uri>http://www.blogger.com/profile/14769199738243128956</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="http://3.bp.blogspot.com/_ZXENRUvBme8/Sd1VsOCz0yI/AAAAAAAAAS4/rhRM7bx1PqI/S220/avatar.jpg" /></author><thr:total>0</thr:total><feedburner:origLink>http://blog.appenginefan.com/2010/04/behind-scenes.html</feedburner:origLink></entry><entry gd:etag="W/&quot;DEEEQXs-eSp7ImA9WxFTGU4.&quot;"><id>tag:blogger.com,1999:blog-5502193732299119611.post-3688398485765613604</id><published>2010-04-10T14:22:00.000-07:00</published><updated>2010-04-10T14:56:40.551-07:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2010-04-10T14:56:40.551-07:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="patterns-of-doom" /><title>Patterns of Doom: the staircase of gets</title><content type="html">&lt;i&gt;Edit: I just realized that the first post on this blog went live on April 10th 2008. Happy birthday, blog.appenginefan.com :-). Any posts from the past you enjoyed? Let me know through comments on this article.&lt;/i&gt;&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_ZXENRUvBme8/S8DvahU_9CI/AAAAAAAAAXA/KEe2AyMhjbk/s1600/staircase_of_gets.jpg"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 320px; height: 91px;" src="http://4.bp.blogspot.com/_ZXENRUvBme8/S8DvahU_9CI/AAAAAAAAAXA/KEe2AyMhjbk/s320/staircase_of_gets.jpg" alt="" id="BLOGGER_PHOTO_ID_5458625987275125794" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;I have received good feedback for my last post, so I thought I'd follow up with a second pattern :-). This pattern is originally from &lt;a href="http://blog.notdot.net/2010/01/ReferenceProperty-prefetching-in-App-Engine"&gt;post by Nick Johnson&lt;/a&gt;, where you can find much more details than here. The staircase of gets usually looks like a long rpc plateau, followed by a subsequent chain of little get requests. The reason behind is often running a loop on the results of a datastore query, and then doing something that causes additional data to be loaded (e.g. additional models via reference properties):&lt;br /&gt;&lt;br /&gt;&lt;pre style="font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; color: #000000; background-color: #eee;font-size: 12px;border: 1px dashed #999999;line-height: 14px;padding: 5px; overflow: auto; width: 100%"&gt;&lt;code&gt;posts = Post.all().fetch(20)&lt;br /&gt;for post in posts:&lt;br /&gt;  self.response.out.write(post.title)&lt;br /&gt;  #post.author is REFERENCE PROPERTY!!!&lt;br /&gt;  self.response.out.write(post.author.name)&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Each call to get the name out of the reference property "author" will trigger an additional datastore get. Thus, the solution is very similar as in Chain of Gets: find a way to collect all the references needed in advance and fetch them in one big datastore get. The code on this page uses the&lt;span class="Apple-converted-space"&gt; &lt;/span&gt;&lt;code&gt;prefetch_refprops&lt;/code&gt;&lt;span class="Apple-converted-space"&gt; &lt;/span&gt;method suggested by Nick. I have made only a small addition (the if statement) that helped provide certain failure scenarios when the referenced property was not set or had been deleted from the store:&lt;br /&gt;&lt;br /&gt;&lt;pre style="font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; color: #000000; background-color: #eee;font-size: 12px;border: 1px dashed #999999;line-height: 14px;padding: 5px; overflow: auto; width: 100%"&gt;&lt;code&gt;def prefetch_refprops(entities, *props):&lt;br /&gt;   fields = [(entity, prop) for entity in entities for prop in props]&lt;br /&gt;   ref_keys = [prop.get_value_for_datastore(x) for x, prop in fields]&lt;br /&gt;   ref_entities = dict((x.key(), x) for x in db.get(set(ref_keys)))&lt;br /&gt;   for (entity, prop), ref_key in zip(fields, ref_keys):&lt;br /&gt;       if ref_entities[ref_key]:&lt;br /&gt;         prop.__set__(entity, ref_entities[ref_key])&lt;br /&gt;   return entities&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;With this method, it is possible to avoid the staircase of gets by just adding a simple method call:&lt;br /&gt;&lt;br /&gt;&lt;pre style="font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; color: #000000; background-color: #eee;font-size: 12px;border: 1px dashed #999999;line-height: 14px;padding: 5px; overflow: auto; width: 100%"&gt;&lt;code&gt;posts = Post.all().fetch(20)&lt;br /&gt;prefetch_refprops(posts, Post.author)&lt;br /&gt;for post in posts:&lt;br /&gt;  self.response.out.write(post.title)&lt;br /&gt;  self.response.out.write(post.author.name)&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;You can check out the code that produced this sampe from the &lt;a href="http://3.latest.googtst23.appspot.com/stats/"&gt;app stats details page&lt;/a&gt;. If you want to re-run it and look at a freshly generated graph, click &lt;a href="http://3.latest.googtst23.appspot.com/staircase_of_gets"&gt;here&lt;/a&gt; (please be gentle; this is running on free quota ;-). And again: if you know good examples you'd like to add to the site, please let me know.&lt;div class="blogger-post-footer"&gt;&lt;script language="javascript" src="http://tipjoy.com/custombutton?targetUser=schefflerjens" scrolling="no" frameborder="0" marginwidth="0" marginheight="0" hspace="0" vspace="0" allowtransparency="true" &gt;&lt;/script&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5502193732299119611-3688398485765613604?l=blog.appenginefan.com' alt='' /&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/TfmSSk8aK0Cz9myvMLQ8cEWkWSY/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/TfmSSk8aK0Cz9myvMLQ8cEWkWSY/0/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;br/&gt;
&lt;a href="http://feedads.g.doubleclick.net/~a/TfmSSk8aK0Cz9myvMLQ8cEWkWSY/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/TfmSSk8aK0Cz9myvMLQ8cEWkWSY/1/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/AppEngineFan?a=Tw10v45bBWs:W9tnUFZbWZc:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/AppEngineFan?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/AppEngineFan?a=Tw10v45bBWs:W9tnUFZbWZc:4cEx4HpKnUU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/AppEngineFan?i=Tw10v45bBWs:W9tnUFZbWZc:4cEx4HpKnUU" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/AppEngineFan?a=Tw10v45bBWs:W9tnUFZbWZc:gIN9vFwOqvQ"&gt;&lt;img src="http://feeds.feedburner.com/~ff/AppEngineFan?i=Tw10v45bBWs:W9tnUFZbWZc:gIN9vFwOqvQ" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://blog.appenginefan.com/feeds/3688398485765613604/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=5502193732299119611&amp;postID=3688398485765613604" title="4 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/5502193732299119611/posts/default/3688398485765613604?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/5502193732299119611/posts/default/3688398485765613604?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/AppEngineFan/~3/Tw10v45bBWs/patterns-of-doom-staircase-of-gets.html" title="Patterns of Doom: the staircase of gets" /><author><name>The App Engine Fan</name><uri>http://www.blogger.com/profile/14769199738243128956</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="http://3.bp.blogspot.com/_ZXENRUvBme8/Sd1VsOCz0yI/AAAAAAAAAS4/rhRM7bx1PqI/S220/avatar.jpg" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://4.bp.blogspot.com/_ZXENRUvBme8/S8DvahU_9CI/AAAAAAAAAXA/KEe2AyMhjbk/s72-c/staircase_of_gets.jpg" height="72" width="72" /><thr:total>4</thr:total><feedburner:origLink>http://blog.appenginefan.com/2010/04/patterns-of-doom-staircase-of-gets.html</feedburner:origLink></entry><entry gd:etag="W/&quot;C08HRXk_eCp7ImA9WxFTFE8.&quot;"><id>tag:blogger.com,1999:blog-5502193732299119611.post-2465100587933953393</id><published>2010-04-04T15:06:00.000-07:00</published><updated>2010-04-04T15:57:14.740-07:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2010-04-04T15:57:14.740-07:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="patterns-of-doom" /><title>Patterns of Doom</title><content type="html">&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_ZXENRUvBme8/S7kQMlNJoDI/AAAAAAAAAWw/PJ-iZOM_UvI/s1600/chainOfGets.jpg"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 320px; height: 92px;" src="http://3.bp.blogspot.com/_ZXENRUvBme8/S7kQMlNJoDI/AAAAAAAAAWw/PJ-iZOM_UvI/s320/chainOfGets.jpg" alt="" id="BLOGGER_PHOTO_ID_5456410231867809842" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;a href="http://googleappengine.blogspot.com/2010/03/easy-performance-profiling-with.html"&gt;App stats&lt;/a&gt; is a great tool for analyzing what is going wrong with the performance of one's app.  On top of that, I also think it is a great learning tool. Thus, I started a "&lt;a href="http://aef.appspot.com/doom"&gt;patterns of doom&lt;/a&gt;" site that can produce common snags and render their result in app stats. If you have anything that you believe happens to developers out there a lot and would like to add to the site, let me know.&lt;br /&gt;&lt;br /&gt;Today's pattern is the "chain of gets": this little friend could also be called "death by a thousand cuts". It will perform a series of gets from the store, for example in a loop. Since each of these gets is an individual RPC, it will take additional time to perform. The pattern in app stats looks like a progression of small time slices; each of them very small, but the sum of them enormous. The better solution: fetch all those models in a single get.&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_ZXENRUvBme8/S7kZJ7GgcdI/AAAAAAAAAW4/SD-QZTbBbPg/s1600/chainOfGets_resolved.jpg"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 320px; height: 16px;" src="http://3.bp.blogspot.com/_ZXENRUvBme8/S7kZJ7GgcdI/AAAAAAAAAW4/SD-QZTbBbPg/s320/chainOfGets_resolved.jpg" alt="" id="BLOGGER_PHOTO_ID_5456420081810567634" border="0" /&gt;&lt;/a&gt;You can check out the code that produced this sampe from the app stats details page.  If you want to re-run it and look at a freshly generated graph, click &lt;a href="http://3.latest.googtst23.appspot.com/chain_of_gets"&gt;here&lt;/a&gt; (please be gentle; this is running on free quota ;-). And again: if you know good examples you'd like to add to the site, please let me know.&lt;div class="blogger-post-footer"&gt;&lt;script language="javascript" src="http://tipjoy.com/custombutton?targetUser=schefflerjens" scrolling="no" frameborder="0" marginwidth="0" marginheight="0" hspace="0" vspace="0" allowtransparency="true" &gt;&lt;/script&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5502193732299119611-2465100587933953393?l=blog.appenginefan.com' alt='' /&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/34da_JogFf9f1afZM10yU0HTFgI/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/34da_JogFf9f1afZM10yU0HTFgI/0/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;br/&gt;
&lt;a href="http://feedads.g.doubleclick.net/~a/34da_JogFf9f1afZM10yU0HTFgI/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/34da_JogFf9f1afZM10yU0HTFgI/1/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/AppEngineFan?a=tL_mICcm-S0:6P6hmPN-zeU:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/AppEngineFan?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/AppEngineFan?a=tL_mICcm-S0:6P6hmPN-zeU:4cEx4HpKnUU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/AppEngineFan?i=tL_mICcm-S0:6P6hmPN-zeU:4cEx4HpKnUU" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/AppEngineFan?a=tL_mICcm-S0:6P6hmPN-zeU:gIN9vFwOqvQ"&gt;&lt;img src="http://feeds.feedburner.com/~ff/AppEngineFan?i=tL_mICcm-S0:6P6hmPN-zeU:gIN9vFwOqvQ" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://blog.appenginefan.com/feeds/2465100587933953393/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=5502193732299119611&amp;postID=2465100587933953393" title="1 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/5502193732299119611/posts/default/2465100587933953393?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/5502193732299119611/posts/default/2465100587933953393?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/AppEngineFan/~3/tL_mICcm-S0/patterns-of-doom.html" title="Patterns of Doom" /><author><name>The App Engine Fan</name><uri>http://www.blogger.com/profile/14769199738243128956</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="http://3.bp.blogspot.com/_ZXENRUvBme8/Sd1VsOCz0yI/AAAAAAAAAS4/rhRM7bx1PqI/S220/avatar.jpg" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://3.bp.blogspot.com/_ZXENRUvBme8/S7kQMlNJoDI/AAAAAAAAAWw/PJ-iZOM_UvI/s72-c/chainOfGets.jpg" height="72" width="72" /><thr:total>1</thr:total><feedburner:origLink>http://blog.appenginefan.com/2010/04/patterns-of-doom.html</feedburner:origLink></entry><entry gd:etag="W/&quot;D04MR3Y9eCp7ImA9WxBbEko.&quot;"><id>tag:blogger.com,1999:blog-5502193732299119611.post-3557011602286178414</id><published>2010-03-09T19:12:00.000-08:00</published><updated>2010-03-10T18:53:06.860-08:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2010-03-10T18:53:06.860-08:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="Startup" /><category scheme="http://www.blogger.com/atom/ns#" term="Opinion" /><title>another Campfire One :-)</title><content type="html">&lt;span style="font-weight:bold;"&gt;&lt;a href="http://googleappsdeveloper.blogspot.com/2010/03/reach-new-customers-integrate-with.html"&gt;Update: the video and more information can be reached from the apps developer blog.&lt;/a&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;When about a year and a half ago the first "labs" services for Google Apps appeared, the &lt;a href="http://googleappengine.blogspot.com/2008/10/when-we-find-tools-that-work-well-for.html"&gt;blog post&lt;/a&gt; mentioned that&lt;br /&gt;&lt;br /&gt;&lt;blockquote&gt;We also intend to eventually open up this platform to all App Engine developers, so that new and existing software vendors can build easy-to-consume software for the million-plus businesses using Google Apps today. &lt;/blockquote&gt;&lt;br /&gt;&lt;br /&gt;Since this announcement, is has been relatively silent around this topic. While Google has been occasionally &lt;a href="http://blog.appenginefan.com/2009/08/breaking-out-of-your-domain.html"&gt; rolling out features that provided a glimpse&lt;/a&gt; of things to come, the bigger integration story had never been told. Until tonight, that is.&lt;br /&gt;&lt;br /&gt;The first article that I saw on Google News was from &lt;a href="http://venturebeat.com/2010/03/09/google-apps-marketplace/"&gt;venturebeat.com&lt;/a&gt;, mentioning a Campfire One that has happened only hours ago:&lt;br /&gt;&lt;br /&gt;&lt;blockquote&gt;The search giant just announced a new service called the Google Apps Marketplace.&lt;br /&gt;&lt;br /&gt;As the name suggests, it’s an online store where third-party developers can sell their applications to the 25 million users of Google Apps.&lt;/blockquote&gt;&lt;br /&gt;&lt;br /&gt;&lt;object width="425" height="344"&gt;&lt;param name="movie" value="http://www.youtube.com/v/QYKZFL7ppMI&amp;color1=0xb1b1b1&amp;color2=0xcfcfcf&amp;hl=en_US&amp;feature=player_embedded&amp;fs=1"&gt;&lt;/param&gt;&lt;param name="allowFullScreen" value="true"&gt;&lt;/param&gt;&lt;param name="allowScriptAccess" value="always"&gt;&lt;/param&gt;&lt;embed src="http://www.youtube.com/v/QYKZFL7ppMI&amp;color1=0xb1b1b1&amp;color2=0xcfcfcf&amp;hl=en_US&amp;feature=player_embedded&amp;fs=1" type="application/x-shockwave-flash" allowfullscreen="true" allowScriptAccess="always" width="425" height="344"&gt;&lt;/embed&gt;&lt;/object&gt;&lt;br /&gt;&lt;br /&gt;The presentation was really exciting -- especially when different software companies took the stage and demonstrated how tightly integrated their applications with Google Apps were. Think of apps that are able to&lt;br /&gt;&lt;br /&gt;&lt;ul&gt;&lt;br /&gt;&lt;li&gt;Use a Google Apps account for sharing login credentials via OpenID&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Securely access google data like docs or calendar via GData and OAuth&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Tie together Google Apps, Salesforce, and other web services seamlessly&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Tie into and display additional data within Gmail&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Be listed and installed from a central Apps Marketplace, where customers can discover&lt;br /&gt;  great enterprise apps -- and developers monetize them.&lt;/li&gt;&lt;br /&gt;&lt;/ul&gt;&lt;br /&gt;&lt;br /&gt;It will be exciting to see if the creation of such a market place will do to the apps infrastructure what other apps marketplaces have done for example to the Android phone or the iPhone. Will we hear new success stories about small hobby developers who build insanely popular enterprise apps and become the next software business sensation? Time will tell.&lt;br /&gt;&lt;br /&gt;Congratulations on the launch -- I'm looking forward to see what happens next!&lt;div class="blogger-post-footer"&gt;&lt;script language="javascript" src="http://tipjoy.com/custombutton?targetUser=schefflerjens" scrolling="no" frameborder="0" marginwidth="0" marginheight="0" hspace="0" vspace="0" allowtransparency="true" &gt;&lt;/script&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5502193732299119611-3557011602286178414?l=blog.appenginefan.com' alt='' /&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/HQrTYvBMFe1AX6dEFnwyh8-nma0/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/HQrTYvBMFe1AX6dEFnwyh8-nma0/0/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;br/&gt;
&lt;a href="http://feedads.g.doubleclick.net/~a/HQrTYvBMFe1AX6dEFnwyh8-nma0/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/HQrTYvBMFe1AX6dEFnwyh8-nma0/1/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/AppEngineFan?a=RXOG-L5iTxw:VsoG13pyIpM:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/AppEngineFan?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/AppEngineFan?a=RXOG-L5iTxw:VsoG13pyIpM:4cEx4HpKnUU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/AppEngineFan?i=RXOG-L5iTxw:VsoG13pyIpM:4cEx4HpKnUU" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/AppEngineFan?a=RXOG-L5iTxw:VsoG13pyIpM:gIN9vFwOqvQ"&gt;&lt;img src="http://feeds.feedburner.com/~ff/AppEngineFan?i=RXOG-L5iTxw:VsoG13pyIpM:gIN9vFwOqvQ" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://blog.appenginefan.com/feeds/3557011602286178414/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=5502193732299119611&amp;postID=3557011602286178414" title="1 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/5502193732299119611/posts/default/3557011602286178414?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/5502193732299119611/posts/default/3557011602286178414?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/AppEngineFan/~3/RXOG-L5iTxw/another-campfire-one.html" title="another Campfire One :-)" /><author><name>The App Engine Fan</name><uri>http://www.blogger.com/profile/14769199738243128956</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="http://3.bp.blogspot.com/_ZXENRUvBme8/Sd1VsOCz0yI/AAAAAAAAAS4/rhRM7bx1PqI/S220/avatar.jpg" /></author><thr:total>1</thr:total><feedburner:origLink>http://blog.appenginefan.com/2010/03/another-campfire-one.html</feedburner:origLink></entry><entry gd:etag="W/&quot;AkIBSHk8eSp7ImA9WxBUGUQ.&quot;"><id>tag:blogger.com,1999:blog-5502193732299119611.post-8776433311665000598</id><published>2010-03-07T13:29:00.000-08:00</published><updated>2010-03-07T13:49:19.771-08:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2010-03-07T13:49:19.771-08:00</app:edited><title>Random rants and stuff to read</title><content type="html">I don't really twitter or send facebook or buzz status updates, so here's a snippet with stuff that's not really enough material for a full post. Sorry if it's a bit short; if you prefer better thought-out posts,  here are two interesting things I ran into this week. Enjoy :-)&lt;div&gt;&lt;ul&gt;&lt;li&gt;&lt;a href="http://vectorpoem.com/news/?p=74"&gt;Lessons from Doom&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="http://blog.cubeofm.com/your-high-iq-will-kill-your-startup"&gt;Your high IQ will kill your startup&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Let's talk about the Nook from Barnes and Noble. I don't buy a lot of gadgets, but this one I own. Reason to choose it over the Kindle was its support for open formats (I love the fact that I can get books from my library online) and the lend me feature, which was advertised as the possibility to borrow e-books from other users. In fact, the &lt;a href="http://blog.appenginefan.com/2010/02/serviceasync-we-dont-need-no-stinking.html"&gt;recent post about gdata apis&lt;/a&gt; was a simplified version of a gdata booksearch, part of an app I was building to make it easier to find other Nook owners to borrow books from. However, I scrapped that effort when I learned that the lend me feature &lt;a href="http://www.mobileread.com/forums/showthread.php?t=60148"&gt;only works once per book.&lt;/a&gt; You heard right: if I buy an ebook from Barnes and Noble, I can lend it out once, and never again. This idea is almost as bad as making a computer game that is played offline but still &lt;a href="http://www.escapistmagazine.com/news/view/98927-Ubisoft-DRM-Authentication-Servers-Go-Down"&gt;relies on serves on the Internet being up&lt;/a&gt;.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Here's the cool part however: I was at the B&amp;amp;N in Santa Clara yesterday and chatted with the sales rep. Turns out that some of the Nook Developers come to the store for a Q&amp;amp;A once per month. Like so many other beloved software products, the Nook gets developed in the Bay area. I doubt that they will be able to give any satisfying answer to the "why" (it's probably more a legal or political than a technical restriction), but the fact that one &lt;i&gt;can&lt;/i&gt; reach out to the devs is a pretty nice thing. I love living in the Silicon Valley :-)&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;script language="javascript" src="http://tipjoy.com/custombutton?targetUser=schefflerjens" scrolling="no" frameborder="0" marginwidth="0" marginheight="0" hspace="0" vspace="0" allowtransparency="true" &gt;&lt;/script&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5502193732299119611-8776433311665000598?l=blog.appenginefan.com' alt='' /&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/WX-VRbLxGjcUnq3EnGnml2ezSlY/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/WX-VRbLxGjcUnq3EnGnml2ezSlY/0/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;br/&gt;
&lt;a href="http://feedads.g.doubleclick.net/~a/WX-VRbLxGjcUnq3EnGnml2ezSlY/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/WX-VRbLxGjcUnq3EnGnml2ezSlY/1/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/AppEngineFan?a=evpS3O8gQcA:9UuY7EhQStY:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/AppEngineFan?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/AppEngineFan?a=evpS3O8gQcA:9UuY7EhQStY:4cEx4HpKnUU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/AppEngineFan?i=evpS3O8gQcA:9UuY7EhQStY:4cEx4HpKnUU" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/AppEngineFan?a=evpS3O8gQcA:9UuY7EhQStY:gIN9vFwOqvQ"&gt;&lt;img src="http://feeds.feedburner.com/~ff/AppEngineFan?i=evpS3O8gQcA:9UuY7EhQStY:gIN9vFwOqvQ" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;</content><link rel="replies" type="application/atom+xml" href="http://blog.appenginefan.com/feeds/8776433311665000598/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=5502193732299119611&amp;postID=8776433311665000598" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/5502193732299119611/posts/default/8776433311665000598?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/5502193732299119611/posts/default/8776433311665000598?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/AppEngineFan/~3/evpS3O8gQcA/random-rants-and-stuff-to-read.html" title="Random rants and stuff to read" /><author><name>The App Engine Fan</name><uri>http://www.blogger.com/profile/14769199738243128956</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="http://3.bp.blogspot.com/_ZXENRUvBme8/Sd1VsOCz0yI/AAAAAAAAAS4/rhRM7bx1PqI/S220/avatar.jpg" /></author><thr:total>0</thr:total><feedburner:origLink>http://blog.appenginefan.com/2010/03/random-rants-and-stuff-to-read.html</feedburner:origLink></entry></feed>

