<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Zustandsforschung &#187; appengine</title>
	<atom:link href="http://www.zustandsforschung.de/index.php/tag/appengine/feed/" rel="self" type="application/rss+xml" />
	<link>http://www.zustandsforschung.de</link>
	<description>Mit offenen Augen durch die Welt</description>
	<lastBuildDate>Wed, 04 Jan 2012 13:22:09 +0000</lastBuildDate>
	<generator>http://wordpress.org/?v=2.8.6</generator>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
			<item>
		<title>Programmieren mit der Google App Engine</title>
		<link>http://www.zustandsforschung.de/index.php/programmieren-mit-der-google-app-engine/</link>
		<comments>http://www.zustandsforschung.de/index.php/programmieren-mit-der-google-app-engine/#comments</comments>
		<pubDate>Wed, 19 Aug 2009 17:15:19 +0000</pubDate>
		<dc:creator>Benedikt</dc:creator>
				<category><![CDATA[Allgemeines]]></category>
		<category><![CDATA[appengine]]></category>
		<category><![CDATA[google]]></category>
		<category><![CDATA[programmierung]]></category>
		<category><![CDATA[python]]></category>

		<guid isPermaLink="false">http://www.zustandsforschung.de/?p=810</guid>
		<description><![CDATA[Was ist die Google App Engine?
Die Google App Engine ist die Cloud-Lösung von Google. Das Prinzip ist, dass man sich in der Implementierung seiner Applikation an die Vorgaben von Google hält und im Anschluss daran seine Applikation auf die Server von Google hochlädt und dort deren Ressourcen bis zu einem gewissen Grad kostenlos nutzen kann. [...]]]></description>
			<content:encoded><![CDATA[<h2>Was ist die Google App Engine?</h2>
<p>Die Google App Engine ist die Cloud-Lösung von Google. Das Prinzip ist, dass man sich in der Implementierung seiner Applikation an die Vorgaben von Google hält und im Anschluss daran seine Applikation auf die Server von Google hochlädt und dort deren Ressourcen bis zu einem gewissen Grad kostenlos nutzen kann. Darüberhinaus hat man die Möglichkeit gegen Bezahlung weitere Ressourcen zu benutzen. Die Vorgaben sind entweder Python oder Java als Programmiersprache zu verwenden und gewisse Einschränkungen bei der Implementierung zu beachten, wie zum Beispiel keine direkten Filesystemzugriffe zu machen.</p>
<p>Im Gegenzug bekommt man eine API, mit der man auf die von Google zu Verfügung gestellte Infrastruktur zugreifen kann. Daten werden beispielsweise nicht in einer Datenbank gespeichert, sondern im Bigtable-Datastore. Das fühlt sich im ersten Moment an wie eine Datenbank, hat allerdings z.B. die Einschränkung keine Joins zu unterstützen, wodurch man ab und zu dazu gezwungen ist, seine Daten zwecks besserer Performance zu denormalisieren (siehe Artikel <a href="http://highscalability.com/how-i-learned-stop-worrying-and-love-using-lot-disk-space-scale">How I Learned to Stop Worrying and Love Using a Lot of Disk Space to Scale</a>).</p>
<h2>Das Projekt</h2>
<p>Eins gleich vorweg: Ich zeige euch hier nicht, wie ihr mit der Google App Engine ein Blog implementiert. Ich glaube es gibt tausende Tutorials, die das tun. In diesem Artikel werde ich mit der Python-Version der Google App Engine eine kleine Applikation implementieren, die von Twitter die neuesten Tweets zieht, die mit dem Hashtag &#8220;#confession&#8221; getaggt sind. Wie die fertige Applikation einmal aussehen soll, könnt ihr euch hier ansehen: <a href="http://confessiontweets.appspot.com">http://confessiontweets.appspot.com</a>.</p>
<p>Ich werde die wichtigsten Features der Google App Engine hier jeweils kurz anreißen &#8211; weitere Details findet ihr in der sehr guten, aber leider nicht auf Deutsch verfügbaren Anleitung unter <a href="http://code.google.com/appengine/">http://code.google.com/appengine/</a>. Ich bitte meine manchmal vielleicht ein bisschen unbeholfene Art, Python zu implementieren zu entschuldigen &#8211; eigentlich komme ich aus der Java-Welt und habe die Google App Engine auch dazu benutzt, ein bisschen Python zu lernen.<br />
<span id="more-810"></span><br />
Der vollständige Source-Code kann hier heruntergeladen werden: <a href="http://www.zustandsforschung.de/wp-content/uploads/2009/07/ConfessionTweets.zip">ConfessionTweets.zip</a></p>
<p><strong>Update:</strong> Der Source-Code ist jetzt auch auf GitHub zu finden: <a href="https://github.com/zustandsforschung/ConfessionTweets">https://github.com/zustandsforschung/ConfessionTweets</a>.</p>
<h2>Die Applikation</h2>
<p>Die Applikation selbst basiert auf dem mit der App Engine mitgelieferten <a href="http://code.google.com/intl/de-DE/appengine/docs/python/tools/webapp/">webapp Framework</a>. Der Einstiegspunkt in die Applikation ist der folgende Code in der Datei &#8220;<em>controller.py</em>&#8220;:</p>

<div class="wp_syntax"><div class="code"><pre class="python" style="font-family:monospace;">application = webapp.<span style="color: black;">WSGIApplication</span><span style="color: black;">&#40;</span><span style="color: black;">&#91;</span><span style="color: black;">&#40;</span><span style="color: #483d8b;">'/'</span>, ConfessionListPage<span style="color: black;">&#41;</span>,
                                      <span style="color: black;">&#40;</span><span style="color: #483d8b;">'/c/.*'</span>, SingleConfessionPage<span style="color: black;">&#41;</span>,
                                      <span style="color: black;">&#40;</span><span style="color: #483d8b;">'/ajax'</span>, AjaxConfessionService<span style="color: black;">&#41;</span><span style="color: black;">&#93;</span><span style="color: black;">&#41;</span>
&nbsp;
<span style="color: #ff7700;font-weight:bold;">def</span> main<span style="color: black;">&#40;</span><span style="color: black;">&#41;</span>:
  run_wsgi_app<span style="color: black;">&#40;</span>application<span style="color: black;">&#41;</span>
&nbsp;
<span style="color: #ff7700;font-weight:bold;">if</span> __name__ == <span style="color: #483d8b;">&quot;__main__&quot;</span>:
  main<span style="color: black;">&#40;</span><span style="color: black;">&#41;</span></pre></div></div>

<p>Hier wird im Wesentlichen definiert, dass Zugriffe auf &#8220;<em>/</em>&#8221; auf die Klasse &#8220;<em>ConfessionListPage</em>&#8221; gemappt werden sollen, Zugriffe auf &#8220;<em>/c/.*</em>&#8221; &#8211; also auf alles was mit &#8220;<em>/c/</em>&#8221; anfängt auf die Klasse &#8220;<em>SingleConfessionPage</em>&#8221; und Zugriffe auf &#8220;<em>/ajax</em>&#8221; auf die Klasse &#8220;<em>AjaxConfessionService</em>&#8220;.<br />
Für die beiden Klassen, die einzelne Seiten darstellen, habe ich eine gemeinsame Superklasse &#8220;<em>Page</em>&#8221; geschrieben, die bereits einige Variablen bereitstellt, die von beiden Klassen benötigt werden. Diese Klasse erbt selbst von &#8220;<em>webapp.RequestHandler</em>&#8220;, und hat damit deren Funktion als Einsprungpunkt in die Applikation.</p>

<div class="wp_syntax"><div class="code"><pre class="python" style="font-family:monospace;"><span style="color: #ff7700;font-weight:bold;">class</span> Page<span style="color: black;">&#40;</span>webapp.<span style="color: black;">RequestHandler</span><span style="color: black;">&#41;</span>:
  collection = <span style="color: #008000;">None</span>
  template_values = <span style="color: black;">&#123;</span><span style="color: black;">&#125;</span>
  template_url = <span style="color: #483d8b;">''</span>
&nbsp;
  <span style="color: #ff7700;font-weight:bold;">def</span> get<span style="color: black;">&#40;</span><span style="color: #008000;">self</span><span style="color: black;">&#41;</span>:
&nbsp;
    header_path = <span style="color: #dc143c;">os</span>.<span style="color: black;">path</span>.<span style="color: black;">join</span><span style="color: black;">&#40;</span><span style="color: #dc143c;">os</span>.<span style="color: black;">path</span>.<span style="color: black;">dirname</span><span style="color: black;">&#40;</span>__file__<span style="color: black;">&#41;</span>, <span style="color: #483d8b;">'../templates/header.html'</span><span style="color: black;">&#41;</span>
    footer_path = <span style="color: #dc143c;">os</span>.<span style="color: black;">path</span>.<span style="color: black;">join</span><span style="color: black;">&#40;</span><span style="color: #dc143c;">os</span>.<span style="color: black;">path</span>.<span style="color: black;">dirname</span><span style="color: black;">&#40;</span>__file__<span style="color: black;">&#41;</span>, <span style="color: #483d8b;">'../templates/footer.html'</span><span style="color: black;">&#41;</span>
&nbsp;
    live = <span style="color: #483d8b;">'localhost'</span> <span style="color: #ff7700;font-weight:bold;">not</span> <span style="color: #ff7700;font-weight:bold;">in</span> <span style="color: #008000;">self</span>.<span style="color: black;">request</span>.<span style="color: black;">host</span>    
&nbsp;
    <span style="color: #008000;">self</span>.<span style="color: black;">template_values</span>.<span style="color: black;">update</span><span style="color: black;">&#40;</span><span style="color: black;">&#123;</span> <span style="color: #483d8b;">'header_path'</span> : header_path,
                                 <span style="color: #483d8b;">'footer_path'</span> : footer_path,
                                 <span style="color: #483d8b;">'live'</span> : live <span style="color: black;">&#125;</span><span style="color: black;">&#41;</span>
&nbsp;
    <span style="color: #008000;">self</span>.<span style="color: black;">page_title</span> = <span style="color: #483d8b;">'ConfessionTweets'</span>
&nbsp;
    <span style="color: #008000;">self</span>.<span style="color: black;">service</span><span style="color: black;">&#40;</span><span style="color: black;">&#41;</span>
&nbsp;
    <span style="color: #008000;">self</span>.<span style="color: black;">template_values</span>.<span style="color: black;">update</span><span style="color: black;">&#40;</span><span style="color: black;">&#123;</span> <span style="color: #483d8b;">'page_title'</span> : <span style="color: #008000;">self</span>.<span style="color: black;">page_title</span> <span style="color: black;">&#125;</span><span style="color: black;">&#41;</span>
&nbsp;
    path = <span style="color: #dc143c;">os</span>.<span style="color: black;">path</span>.<span style="color: black;">join</span><span style="color: black;">&#40;</span><span style="color: #dc143c;">os</span>.<span style="color: black;">path</span>.<span style="color: black;">dirname</span><span style="color: black;">&#40;</span>__file__<span style="color: black;">&#41;</span>, <span style="color: #008000;">self</span>.<span style="color: black;">template_url</span><span style="color: black;">&#41;</span>
    <span style="color: #ff7700;font-weight:bold;">try</span>:
        <span style="color: #008000;">self</span>.<span style="color: black;">response</span>.<span style="color: black;">out</span>.<span style="color: black;">write</span><span style="color: black;">&#40;</span>template.<span style="color: black;">render</span><span style="color: black;">&#40;</span>path, <span style="color: #008000;">self</span>.<span style="color: black;">template_values</span><span style="color: black;">&#41;</span><span style="color: black;">&#41;</span>
    <span style="color: #ff7700;font-weight:bold;">except</span> TemplateDoesNotExist:
        <span style="color: #dc143c;">logging</span>.<span style="color: black;">error</span><span style="color: black;">&#40;</span><span style="color: #483d8b;">'Template %s not found!'</span> <span style="color: #66cc66;">%</span> <span style="color: #008000;">self</span>.<span style="color: black;">template_url</span><span style="color: black;">&#41;</span></pre></div></div>

<p>Die Methode &#8220;<em>get</em>&#8221; wird automatisch vom webapp Framework aufgerufen, wenn eine Seite (per GET-Request) angefragt wurde. Ebenso gibt es Framework-Methoden für POST- oder HEAD-Requests. Dann werden hier einige Werte initialisiert wie beispielsweise der Pfad zu Header und Footer oder der Seitentitel. Dann folgt ein Aufruf der Methode &#8220;<em>service</em>&#8220;, die wie wir gleich sehen werden von den erbenden Klassen implementiert wird. Abschließend wird die Methode &#8220;<em>template.render</em>&#8221; aufgerufen, die als Parameter den Pfad zum Template, das die Seite darstellt und ein Dictionary mit den auf dem Template verwendeten Werten bekommt. Innerhalb dieser Methode werden die Werte in das Template geschrieben und das Resultat in die Response ausgegeben.</p>
<h2>Die ConfessionListPage</h2>
<p>Von dieser Klasse wird nun die Klasse &#8220;<em>ConfessionListPage</em>&#8221; abgeleitet. Diese Klasse ist für die Listendarstellung der einzelnen Tweets verantwortlich.</p>

<div class="wp_syntax"><div class="code"><pre class="python" style="font-family:monospace;"><span style="color: #ff7700;font-weight:bold;">class</span> ConfessionListPage<span style="color: black;">&#40;</span>Page<span style="color: black;">&#41;</span>:
&nbsp;
    <span style="color: #ff7700;font-weight:bold;">def</span> service<span style="color: black;">&#40;</span><span style="color: #008000;">self</span><span style="color: black;">&#41;</span>:
        <span style="color: #008000;">self</span>.<span style="color: black;">template_url</span> = <span style="color: #483d8b;">'../templates/confession_list.html'</span>
&nbsp;
        twitter_request = TwitterRequest<span style="color: black;">&#40;</span><span style="color: black;">&#41;</span>
        tweets = twitter_request.<span style="color: black;">get_latest_tweets</span><span style="color: black;">&#40;</span><span style="color: black;">&#41;</span>
&nbsp;
        <span style="color: #008000;">self</span>.<span style="color: black;">template_values</span>.<span style="color: black;">update</span><span style="color: black;">&#40;</span><span style="color: black;">&#123;</span> <span style="color: #483d8b;">'tweets'</span> : tweets <span style="color: black;">&#125;</span><span style="color: black;">&#41;</span></pre></div></div>

<p>Da alles andere schon in der &#8220;<em>Page</em>&#8220;-Klasse passiert, muss hier nur noch die &#8220;<em>service</em>&#8220;-Methode implementiert werden, die von der Page-Klasse aufgerufen wird. Als erstes wird der Pfad zum Template, also der View für diese Seite definiert. Dann wird mit Hilfe der Klasse &#8220;<em>TwitterRequest</em>&#8220;, die wir gleich implementieren werden, eine Liste von &#8220;<em>Tweets</em>&#8221; erzeugt, die dann mit der Anweisung &#8220;<em>self.template_values.update</em>&#8221; an das Template übergeben werden.</p>
<p>Im Template selbst findet sich der folgende Code:</p>

<div class="wp_syntax"><div class="code"><pre class="html" style="font-family:monospace;">{% include header_path %}
{% for tweet in tweets %}
	&lt;script type=&quot;text/javascript&quot; &gt;
		confessions[&quot;{{ tweet.key.name }}&quot;] = &quot;{{ tweet.key.name }}&quot;;
	&lt;/script&gt;
	{% if forloop.first %}
		&lt;div id=&quot;{{ tweet.key.name }}&quot; class=&quot;first_confession&quot;&gt;
			&lt;h3&gt;&lt;a href=&quot;/c/{{ tweet.key.name }}&quot;&gt;{{ tweet.title }}&lt;/a&gt;&lt;/h3&gt;
			&lt;p&gt;Confessed {{ tweet.published|timesince }} ago by &lt;a href=&quot;{{ tweet.author_url }}&quot;&gt;{{ tweet.author }}&lt;/a&gt;&lt;/p&gt;
		&lt;/div&gt;
	{% else %}
		&lt;div id=&quot;{{ tweet.key.name }}&quot; class=&quot;confession&quot;&gt;
			&lt;h3&gt;&lt;a href=&quot;/c/{{ tweet.key.name }}&quot;&gt;{{ tweet.title }}&lt;/a&gt;&lt;/h3&gt;
			&lt;p&gt;Confessed {{ tweet.published|timesince }} ago by &lt;a href=&quot;{{ tweet.author_url }}&quot;&gt;{{ tweet.author }}&lt;/a&gt;&lt;/p&gt;
		&lt;/div&gt;
	{% endif %}
{% endfor %}
	&lt;div id=&quot;new_confessions&quot; &gt;&lt;/div&gt;
	&amp;darr; &lt;a id=&quot;more_button&quot; href=&quot;javascript:void(0)&quot; onclick=&quot;getConfessions(confessions)&quot; &gt;more&lt;/a&gt; &amp;darr;
{% include footer_path %}</pre></div></div>

<p>Die Templates für die Goolge App Engine werden mit Hilfe des Django-Frameworks erzeugt. Eine Liste von verwendbaren Template-Tags kann man daher der Django-Dokumentation selbst entnehmen: <a href="http://docs.djangoproject.com/en/dev/ref/templates/builtins/">Built-in template tags and filters</a>. Die erste und letzte Zeile binden den Header und Footer entsprechend den in der Klasse &#8220;<em>Page</em>&#8221; definierten Pfaden ein. Dann wird über die Liste der Tweets mittels &#8220;<em>for tweet in tweets</em>&#8221; iteriert. Innerhalb dieser Schleife stehen die einzelnen Listenelemente dann in der Variable &#8220;<em>tweet</em>&#8221; zur Verfügung. Da wir das erste Element dieser Liste speziell formatieren wollen, machen wir von der innerhalb von &#8220;<em>for</em>&#8220;-Schleifen vorhandenen Variable &#8220;<em>forloop.first</em>&#8221; gebrauch: Wenn wir das erste Element haben, dann schreiben wir einen anderen Div um die Ausgabe drumherum als für alle anderen Schleifenelemente. Zur Optimierung könnte man sicherlich nur das &#8220;<em>class</em>&#8220;-Attribut in den if-else-Block setzen, ich habe das hier aber nicht gemacht, um zu verdeutlichen, was mit diesem Template-Tag so alles möglich ist.</p>
<h2>Die SingleConfessionPage</h2>
<p>Da ich auch in der Lage sein möchte, jeden Tweet unter seiner eigenen URL darstellen zu können, habe ich eine Tweet-Klasse implementiert, die alle Daten aus dem Feed im Datastore speichert:</p>

<div class="wp_syntax"><div class="code"><pre class="python" style="font-family:monospace;"><span style="color: #ff7700;font-weight:bold;">class</span> Tweet<span style="color: black;">&#40;</span>db.<span style="color: black;">Model</span><span style="color: black;">&#41;</span>:
    published = db.<span style="color: black;">DateTimeProperty</span><span style="color: black;">&#40;</span>required=<span style="color: #008000;">True</span><span style="color: black;">&#41;</span>
    status_url = db.<span style="color: black;">StringProperty</span><span style="color: black;">&#40;</span>required=<span style="color: #008000;">True</span><span style="color: black;">&#41;</span>
    title = db.<span style="color: black;">StringProperty</span><span style="color: black;">&#40;</span>required=<span style="color: #008000;">True</span><span style="color: black;">&#41;</span>
    content = db.<span style="color: black;">StringProperty</span><span style="color: black;">&#40;</span>required=<span style="color: #008000;">True</span><span style="color: black;">&#41;</span>
    profile_image = db.<span style="color: black;">StringProperty</span><span style="color: black;">&#40;</span><span style="color: black;">&#41;</span>
    author = db.<span style="color: black;">StringProperty</span><span style="color: black;">&#40;</span>required=<span style="color: #008000;">True</span><span style="color: black;">&#41;</span>
    author_url = db.<span style="color: black;">StringProperty</span><span style="color: black;">&#40;</span>required=<span style="color: #008000;">True</span><span style="color: black;">&#41;</span></pre></div></div>

<p>Diese Klasse erbt von der vom App-Engine-Framework zur Verfügung gestellten Basisklasse db.Model und hat damit schon eine Menge Funktionen, wie z.B. die get_or_insert-Methode, die innerhalb der __get_tweet_from_xml__-Methode der TwitterRequest-Klasse aufgerufen wird, und die alle übergebenen Daten auch gleich im Datastore speichert, falls es den gleichen Tweet nicht schon gibt:</p>

<div class="wp_syntax"><div class="code"><pre class="python" style="font-family:monospace;">        tweet = Tweet.<span style="color: black;">get_or_insert</span><span style="color: black;">&#40;</span>key_name=key_name, published=published, status_url=status_url, title=title, content=content, profile_image=profile_image, author=author, author_url=author_url<span style="color: black;">&#41;</span></pre></div></div>

<p>Man muss diese statische Methode nicht benutzen, um ein Objekt im Datastore zu erzeugen, man kann auch einfach den Konstruktor der Klasse verwenden und dann mit der &#8220;<em>put</em>&#8220;-Methode die Daten persistieren:</p>

<div class="wp_syntax"><div class="code"><pre class="python" style="font-family:monospace;">	tweet = Tweet<span style="color: black;">&#40;</span>key_name=key_name, published=published, status_url=status_url, title=title, content=content, profile_image=profile_image, author=author, author_url=author_url<span style="color: black;">&#41;</span>
	tweet.<span style="color: black;">put</span><span style="color: black;">&#40;</span><span style="color: black;">&#41;</span></pre></div></div>

<p>Ich identifiziere dabei die einzelnen Tweets anhand der base-64-codierten Status-URL, die mir von Twitter geliefert wird und die pro Tweet eindeutig ist. Diesen Wert verwende ich nicht nur hier als key_name, sondern auch in der URL für die einzelnen Tweets, die dann etwa  &#8220;<em>/c/eG94T3BhbEFtYW5kYS9zdGF0dXNlcy8yMjgzMTI0MDg5</em>&#8221; lautet. Eine solche URL wird dann vom Controller auf die get-Methode der Klasse SingleConfessionPage gemappt:</p>

<div class="wp_syntax"><div class="code"><pre class="python" style="font-family:monospace;"><span style="color: #ff7700;font-weight:bold;">class</span> SingleConfessionPage<span style="color: black;">&#40;</span>Page<span style="color: black;">&#41;</span>:
&nbsp;
    <span style="color: #ff7700;font-weight:bold;">def</span> service<span style="color: black;">&#40;</span><span style="color: #008000;">self</span><span style="color: black;">&#41;</span>:
        <span style="color: #008000;">self</span>.<span style="color: black;">template_url</span> = <span style="color: #483d8b;">'../templates/confession.html'</span>
&nbsp;
        match = <span style="color: #dc143c;">re</span>.<span style="color: black;">match</span><span style="color: black;">&#40;</span><span style="color: #483d8b;">&quot;/c/(.*)&quot;</span>, <span style="color: #008000;">self</span>.<span style="color: black;">request</span>.<span style="color: black;">path</span><span style="color: black;">&#41;</span>
        <span style="color: #ff7700;font-weight:bold;">if</span> match <span style="color: #ff7700;font-weight:bold;">is</span> <span style="color: #ff7700;font-weight:bold;">not</span> <span style="color: #008000;">None</span>:
            key_name = <span style="color: #dc143c;">urllib</span>.<span style="color: black;">unquote</span><span style="color: black;">&#40;</span>match.<span style="color: black;">group</span><span style="color: black;">&#40;</span><span style="color: #ff4500;">1</span><span style="color: black;">&#41;</span><span style="color: black;">&#41;</span>
&nbsp;
        tweet = Tweet.<span style="color: black;">get_by_key_name</span><span style="color: black;">&#40;</span>key_name<span style="color: black;">&#41;</span>
&nbsp;
        <span style="color: #ff7700;font-weight:bold;">if</span> tweet <span style="color: #ff7700;font-weight:bold;">is</span> <span style="color: #008000;">None</span>:
            <span style="color: #008000;">self</span>.<span style="color: black;">redirect</span><span style="color: black;">&#40;</span><span style="color: #483d8b;">'/'</span><span style="color: black;">&#41;</span>
            <span style="color: #ff7700;font-weight:bold;">return</span>
&nbsp;
        <span style="color: #008000;">self</span>.<span style="color: black;">page_title</span> += <span style="color: #483d8b;">' - A confession by '</span> + tweet.<span style="color: black;">author</span> + <span style="color: #483d8b;">' made '</span> + timesince.<span style="color: black;">timesince</span><span style="color: black;">&#40;</span>tweet.<span style="color: black;">published</span><span style="color: black;">&#41;</span> + <span style="color: #483d8b;">' ago '</span> 
&nbsp;
        <span style="color: #008000;">self</span>.<span style="color: black;">template_values</span>.<span style="color: black;">update</span><span style="color: black;">&#40;</span><span style="color: black;">&#123;</span> <span style="color: #483d8b;">'tweet'</span> : tweet <span style="color: black;">&#125;</span><span style="color: black;">&#41;</span></pre></div></div>

<p>Dort lese ich den Key wieder aus der URL aus, und hole dann den entsprechenden Tweet per &#8220;<em>Tweet.get_by_key_name(key_name)</em>&#8221; aus dem Datastore und übergebe ihn ans Template, das dann eine vereinfachte Variante von dem für die ConfessionListPage ist:</p>

<div class="wp_syntax"><div class="code"><pre class="html" style="font-family:monospace;">	{% include header_path %}
			&lt;div id=&quot;{{ tweet.key.name }}&quot; class=&quot;first_confession&quot;&gt;
				&lt;h3&gt;&lt;a href=&quot;{{ tweet.status_url }}&quot;&gt;{{ tweet.title }}&lt;/a&gt;&lt;/h3&gt;
				&lt;p&gt;Confessed {{ tweet.published|timesince }} ago by &lt;a href=&quot;{{ tweet.author_url }}&quot;&gt;{{ tweet.author }}&lt;/a&gt;&lt;/p&gt;
			&lt;/div&gt;
	{% include footer_path %}</pre></div></div>

<h2>Der TwitterRequest-Service</h2>
<p>Diese Klasse führt in der Methode &#8220;<em>get_latest_tweets</em>&#8221; den eigentlichen Request an Twitter aus, mit dem die Applikation ihre Daten erhält. Dabei wird in der Methode &#8220;<em>__get_xml__</em>&#8221; mittels der urlfetch-Methode des Frameworks von der Twitter-Suche ein XML geholt und in eine DOM-Datenstruktur überführt:</p>

<div class="wp_syntax"><div class="code"><pre class="python" style="font-family:monospace;"><span style="color: #ff7700;font-weight:bold;">class</span> TwitterRequest:
&nbsp;
    <span style="color: #ff7700;font-weight:bold;">def</span> <span style="color: #0000cd;">__init__</span><span style="color: black;">&#40;</span><span style="color: #008000;">self</span><span style="color: black;">&#41;</span>:
        <span style="color: #008000;">self</span>.<span style="color: black;">request_url</span> = <span style="color: #483d8b;">'http://search.twitter.com/search.atom?q=%23confession&amp;amp;rpp=5'</span>
&nbsp;
    <span style="color: #ff7700;font-weight:bold;">def</span> get_latest_tweets<span style="color: black;">&#40;</span><span style="color: #008000;">self</span>, page=<span style="color: #483d8b;">'1'</span><span style="color: black;">&#41;</span>:
        tweets = <span style="color: black;">&#91;</span><span style="color: black;">&#93;</span>
&nbsp;
        <span style="color: #008000;">self</span>.<span style="color: black;">request_url</span> += <span style="color: #483d8b;">'&amp;amp;page='</span> + page<span style="color: #66cc66;">;</span>
        <span style="color: #dc143c;">logging</span>.<span style="color: black;">debug</span><span style="color: black;">&#40;</span><span style="color: #483d8b;">'Requesting %s'</span> <span style="color: #66cc66;">%</span> <span style="color: #008000;">self</span>.<span style="color: black;">request_url</span><span style="color: black;">&#41;</span>
&nbsp;
        twitter_response = <span style="color: #008000;">self</span>.__get_xml__<span style="color: black;">&#40;</span><span style="color: #008000;">self</span>.<span style="color: black;">request_url</span><span style="color: black;">&#41;</span>                
&nbsp;
        <span style="color: #ff7700;font-weight:bold;">if</span> twitter_response <span style="color: #ff7700;font-weight:bold;">is</span> <span style="color: #008000;">None</span>:
            <span style="color: #dc143c;">logging</span>.<span style="color: black;">warning</span><span style="color: black;">&#40;</span><span style="color: #483d8b;">'Trying again...'</span><span style="color: black;">&#41;</span>
            twitter_response = <span style="color: #008000;">self</span>.__get_xml__<span style="color: black;">&#40;</span><span style="color: #008000;">self</span>.<span style="color: black;">request_url</span><span style="color: black;">&#41;</span>
&nbsp;
        <span style="color: #ff7700;font-weight:bold;">if</span> twitter_response <span style="color: #ff7700;font-weight:bold;">is</span> <span style="color: #008000;">None</span>:
            <span style="color: #ff7700;font-weight:bold;">return</span> <span style="color: #008000;">None</span> 
&nbsp;
        <span style="color: #ff7700;font-weight:bold;">for</span> entry <span style="color: #ff7700;font-weight:bold;">in</span> twitter_response.<span style="color: black;">getElementsByTagName</span><span style="color: black;">&#40;</span><span style="color: #483d8b;">'entry'</span><span style="color: black;">&#41;</span>:
            tweets.<span style="color: black;">append</span><span style="color: black;">&#40;</span><span style="color: #008000;">self</span>.__get_tweet_from_xml__<span style="color: black;">&#40;</span>entry<span style="color: black;">&#41;</span><span style="color: black;">&#41;</span>
&nbsp;
        <span style="color: #ff7700;font-weight:bold;">return</span> tweets
&nbsp;
    <span style="color: #ff7700;font-weight:bold;">def</span> __get_xml__<span style="color: black;">&#40;</span><span style="color: #008000;">self</span>, request_url<span style="color: black;">&#41;</span>:
        <span style="color: #ff7700;font-weight:bold;">try</span>:
            result = urlfetch.<span style="color: black;">fetch</span><span style="color: black;">&#40;</span>request_url<span style="color: black;">&#41;</span>
        <span style="color: #ff7700;font-weight:bold;">except</span> DownloadError:
            <span style="color: #dc143c;">logging</span>.<span style="color: black;">error</span><span style="color: black;">&#40;</span><span style="color: #483d8b;">'Could not contact webservice.'</span><span style="color: black;">&#41;</span>
            <span style="color: #ff7700;font-weight:bold;">return</span> <span style="color: #008000;">None</span>
        <span style="color: #ff7700;font-weight:bold;">if</span> result.<span style="color: black;">status_code</span> == <span style="color: #ff4500;">200</span>:
            <span style="color: #ff7700;font-weight:bold;">return</span> parseString<span style="color: black;">&#40;</span>result.<span style="color: black;">content</span><span style="color: black;">&#41;</span>
&nbsp;
    <span style="color: #ff7700;font-weight:bold;">def</span> __get_tweet_from_xml__<span style="color: black;">&#40;</span><span style="color: #008000;">self</span>, entry<span style="color: black;">&#41;</span>:
        published_string = entry.<span style="color: black;">getElementsByTagName</span><span style="color: black;">&#40;</span><span style="color: #483d8b;">'published'</span><span style="color: black;">&#41;</span><span style="color: black;">&#91;</span><span style="color: #ff4500;">0</span><span style="color: black;">&#93;</span>.<span style="color: black;">firstChild</span>.<span style="color: black;">data</span>
        published = <span style="color: #dc143c;">datetime</span>.<span style="color: #dc143c;">datetime</span>.<span style="color: black;">strptime</span><span style="color: black;">&#40;</span>published_string, <span style="color: #483d8b;">&quot;%Y-%m-%dT%H:%M:%SZ&quot;</span><span style="color: black;">&#41;</span>        
&nbsp;
        status_url = entry.<span style="color: black;">getElementsByTagName</span><span style="color: black;">&#40;</span><span style="color: #483d8b;">'link'</span><span style="color: black;">&#41;</span><span style="color: black;">&#91;</span><span style="color: #ff4500;">0</span><span style="color: black;">&#93;</span>.<span style="color: black;">getAttribute</span><span style="color: black;">&#40;</span><span style="color: #483d8b;">'href'</span><span style="color: black;">&#41;</span>
        title = entry.<span style="color: black;">getElementsByTagName</span><span style="color: black;">&#40;</span><span style="color: #483d8b;">'title'</span><span style="color: black;">&#41;</span><span style="color: black;">&#91;</span><span style="color: #ff4500;">0</span><span style="color: black;">&#93;</span>.<span style="color: black;">firstChild</span>.<span style="color: black;">data</span>
        content = entry.<span style="color: black;">getElementsByTagName</span><span style="color: black;">&#40;</span><span style="color: #483d8b;">'content'</span><span style="color: black;">&#41;</span><span style="color: black;">&#91;</span><span style="color: #ff4500;">0</span><span style="color: black;">&#93;</span>.<span style="color: black;">firstChild</span>.<span style="color: black;">data</span>
        author = entry.<span style="color: black;">getElementsByTagName</span><span style="color: black;">&#40;</span><span style="color: #483d8b;">'author'</span><span style="color: black;">&#41;</span><span style="color: black;">&#91;</span><span style="color: #ff4500;">0</span><span style="color: black;">&#93;</span>.<span style="color: black;">getElementsByTagName</span><span style="color: black;">&#40;</span><span style="color: #483d8b;">'name'</span><span style="color: black;">&#41;</span><span style="color: black;">&#91;</span><span style="color: #ff4500;">0</span><span style="color: black;">&#93;</span>.<span style="color: black;">firstChild</span>.<span style="color: black;">data</span>
        author_url = entry.<span style="color: black;">getElementsByTagName</span><span style="color: black;">&#40;</span><span style="color: #483d8b;">'author'</span><span style="color: black;">&#41;</span><span style="color: black;">&#91;</span><span style="color: #ff4500;">0</span><span style="color: black;">&#93;</span>.<span style="color: black;">getElementsByTagName</span><span style="color: black;">&#40;</span><span style="color: #483d8b;">'uri'</span><span style="color: black;">&#41;</span><span style="color: black;">&#91;</span><span style="color: #ff4500;">0</span><span style="color: black;">&#93;</span>.<span style="color: black;">firstChild</span>.<span style="color: black;">data</span>
        profile_image = entry.<span style="color: black;">getElementsByTagName</span><span style="color: black;">&#40;</span><span style="color: #483d8b;">'link'</span><span style="color: black;">&#41;</span><span style="color: black;">&#91;</span><span style="color: #ff4500;">1</span><span style="color: black;">&#93;</span>.<span style="color: black;">getAttribute</span><span style="color: black;">&#40;</span><span style="color: #483d8b;">'href'</span><span style="color: black;">&#41;</span>
&nbsp;
        key_name = <span style="color: #dc143c;">base64</span>.<span style="color: black;">standard_b64encode</span><span style="color: black;">&#40;</span>status_url<span style="color: black;">&#91;</span><span style="color: #008000;">len</span><span style="color: black;">&#40;</span><span style="color: #483d8b;">'http://twitter.com/'</span><span style="color: black;">&#41;</span>:<span style="color: #008000;">len</span><span style="color: black;">&#40;</span>status_url<span style="color: black;">&#41;</span><span style="color: black;">&#93;</span><span style="color: black;">&#41;</span>
        tweet = Tweet.<span style="color: black;">get_or_insert</span><span style="color: black;">&#40;</span>key_name=key_name, published=published, status_url=status_url, title=title, content=content, profile_image=profile_image, author=author, author_url=author_url<span style="color: black;">&#41;</span>
        <span style="color: #ff7700;font-weight:bold;">return</span> tweet</pre></div></div>

<p>Dann rufe ich für jeden &#8220;<em>entry</em>&#8220;-Knoten in diesem XML die &#8220;<em>__get_tweet_from_xml__</em>&#8220;-Methode auf, die die relevanten Daten aus dem Knoten ausliest und letztendlich mit der &#8220;<em>get_or_insert</em>&#8220;-Methode von db.Model die Daten im Datastore abspeichert. Die &#8220;<em>get_latest_tweets</em>&#8220;-Methode gibt schließlich eine Liste mit den einzelnen Tweet-Objekten, die aus dem XML des Webservice abgerufen wurden zurück.</p>
<h2>Der AjaxConfessionService</h2>
<p>Ich möchte auf der Startseite auch die Möglichkeit bieten, durch die Liste der Tweets blättern zu können. Zu diesem Zweck benutze ich eine AJAX-Schnittstelle, um beim Klick auf den more-Link weitere Tweets von Twitter abzuholen und einzublenden. Das JavaScript schickt den Request an den Pfad &#8220;<em>/ajax</em>&#8220;, der auf die Klasse &#8220;<em>AjaxConfessionService</em>&#8221; gemappt wird. Auch diese Klasse erbt wieder von &#8220;<em>webapp.RequestHandler</em>&#8221; und implementiert die &#8220;<em>get</em>&#8220;-Methode:</p>

<div class="wp_syntax"><div class="code"><pre class="python" style="font-family:monospace;"><span style="color: #ff7700;font-weight:bold;">class</span> AjaxConfessionService<span style="color: black;">&#40;</span>webapp.<span style="color: black;">RequestHandler</span><span style="color: black;">&#41;</span>:
&nbsp;
    <span style="color: #ff7700;font-weight:bold;">def</span> get<span style="color: black;">&#40;</span><span style="color: #008000;">self</span><span style="color: black;">&#41;</span>:
        page = <span style="color: #008000;">self</span>.<span style="color: black;">request</span>.<span style="color: black;">get</span><span style="color: black;">&#40;</span><span style="color: #483d8b;">'page'</span>, <span style="color: #483d8b;">''</span><span style="color: black;">&#41;</span><span style="color: #66cc66;">;</span>
&nbsp;
        twitter_request = TwitterRequest<span style="color: black;">&#40;</span><span style="color: black;">&#41;</span>
        tweets = twitter_request.<span style="color: black;">get_latest_tweets</span><span style="color: black;">&#40;</span>page<span style="color: black;">&#41;</span>
        <span style="color: #008000;">self</span>.<span style="color: black;">response</span>.<span style="color: black;">out</span>.<span style="color: black;">write</span><span style="color: black;">&#40;</span><span style="color: #483d8b;">'{ &quot;confessions&quot;: {'</span><span style="color: black;">&#41;</span>
        <span style="color: #ff7700;font-weight:bold;">for</span> tweet <span style="color: #ff7700;font-weight:bold;">in</span> tweets:
            <span style="color: #008000;">self</span>.<span style="color: black;">response</span>.<span style="color: black;">out</span>.<span style="color: black;">write</span><span style="color: black;">&#40;</span><span style="color: #483d8b;">'&quot;'</span> + tweet.<span style="color: black;">key</span><span style="color: black;">&#40;</span><span style="color: black;">&#41;</span>.<span style="color: black;">name</span><span style="color: black;">&#40;</span><span style="color: black;">&#41;</span> + <span style="color: #483d8b;">'&quot; : { '</span><span style="color: black;">&#41;</span>
            <span style="color: #008000;">self</span>.<span style="color: black;">response</span>.<span style="color: black;">out</span>.<span style="color: black;">write</span><span style="color: black;">&#40;</span><span style="color: #483d8b;">'&quot;title&quot; : &quot;'</span> + tweet.<span style="color: black;">title</span>.<span style="color: black;">replace</span><span style="color: black;">&#40;</span><span style="color: #483d8b;">'&quot;'</span>, <span style="color: #483d8b;">'<span style="color: #000099; font-weight: bold;">\\</span>&quot;'</span><span style="color: black;">&#41;</span> + <span style="color: #483d8b;">'&quot;, '</span><span style="color: black;">&#41;</span>
            <span style="color: #008000;">self</span>.<span style="color: black;">response</span>.<span style="color: black;">out</span>.<span style="color: black;">write</span><span style="color: black;">&#40;</span><span style="color: #483d8b;">'&quot;author&quot; : &quot;'</span> + tweet.<span style="color: black;">author</span>.<span style="color: black;">replace</span><span style="color: black;">&#40;</span><span style="color: #483d8b;">'&quot;'</span>, <span style="color: #483d8b;">'<span style="color: #000099; font-weight: bold;">\\</span>&quot;'</span><span style="color: black;">&#41;</span> + <span style="color: #483d8b;">'&quot;, '</span><span style="color: black;">&#41;</span>
            <span style="color: #008000;">self</span>.<span style="color: black;">response</span>.<span style="color: black;">out</span>.<span style="color: black;">write</span><span style="color: black;">&#40;</span><span style="color: #483d8b;">'&quot;author_url&quot; : &quot;'</span> + tweet.<span style="color: black;">author_url</span>.<span style="color: black;">replace</span><span style="color: black;">&#40;</span><span style="color: #483d8b;">'&quot;'</span>, <span style="color: #483d8b;">'<span style="color: #000099; font-weight: bold;">\\</span>&quot;'</span><span style="color: black;">&#41;</span> + <span style="color: #483d8b;">'&quot;, '</span><span style="color: black;">&#41;</span>
            <span style="color: #008000;">self</span>.<span style="color: black;">response</span>.<span style="color: black;">out</span>.<span style="color: black;">write</span><span style="color: black;">&#40;</span><span style="color: #483d8b;">'&quot;published&quot; : &quot;'</span> + timesince.<span style="color: black;">timesince</span><span style="color: black;">&#40;</span>tweet.<span style="color: black;">published</span><span style="color: black;">&#41;</span> + <span style="color: #483d8b;">'&quot;'</span><span style="color: black;">&#41;</span>
            <span style="color: #008000;">self</span>.<span style="color: black;">response</span>.<span style="color: black;">out</span>.<span style="color: black;">write</span><span style="color: black;">&#40;</span><span style="color: #483d8b;">'}'</span><span style="color: black;">&#41;</span>
            <span style="color: #ff7700;font-weight:bold;">if</span> tweet <span style="color: #ff7700;font-weight:bold;">is</span> <span style="color: #ff7700;font-weight:bold;">not</span> tweets<span style="color: black;">&#91;</span>-<span style="color: #ff4500;">1</span><span style="color: black;">&#93;</span>:
                <span style="color: #008000;">self</span>.<span style="color: black;">response</span>.<span style="color: black;">out</span>.<span style="color: black;">write</span><span style="color: black;">&#40;</span><span style="color: #483d8b;">', '</span><span style="color: black;">&#41;</span>
        <span style="color: #008000;">self</span>.<span style="color: black;">response</span>.<span style="color: black;">out</span>.<span style="color: black;">write</span><span style="color: black;">&#40;</span><span style="color: #483d8b;">'}}'</span><span style="color: black;">&#41;</span></pre></div></div>

<p>Hier werden auch wieder die aktuellsten Tweets der entsprechenden Ergebnisseite bei Twitter abgeholt. Hier habe ich kein Template verwendet, sondern wandle die resultierenden Tweet-Objekte direkt in einen JSON-String um, den ich in die Response schreibe. Auf der Clientseite liest jetzt mein JavaScript wiederum das JSON aus und konstruiert HTML, das in die Seite eingefügt wird.</p>
<h2>Konfiguration</h2>
<p>Damit die Applikation überhaupt in der Google-App-Engine lauffähig ist, braucht sie noch eine Konfiguration. Diese wird in der Markup-Language YAML geschrieben und landet in einer Datei &#8220;<em>app.yaml</em>&#8221; im Wurzelverzeichnis der Applikation:</p>

<div class="wp_syntax"><div class="code"><pre class="yaml" style="font-family:monospace;">application: confessiontweets
version: 1
runtime: python
api_version: 1
&nbsp;
handlers:
- url: /robots.txt
  static_files: robots.txt
  upload: robots.txt
&nbsp;
- url: /css
  static_dir: css
  expiration: 1h
&nbsp;
- url: /images
  static_dir: images
  expiration: 1d
&nbsp;
- url: /js
  static_dir: js
  expiration: 60s
&nbsp;
- url: /.*
  script: confession/controller.py</pre></div></div>

<p>Zuoberst muss man das Label seiner Applikation deklarieren, so wie man es später auch in der Verwaltung der App-Engine angibt. Dann kommt die Version der Applikation und die Runtime &#8211; in unserem Fall Python. Die API-Version ist aktuell noch immer 1.</p>
<p>Unterhalb dieser Sektion definiert man nun einzelne Handler für verschiedene Files. Man kann keine der hochgeladenen Files direkt aufrufen, daher definiere ich hier zunächst die Position der robots.txt und der Verzeichnisse für CSS, Bilder und JavaScript, jeweils mit einer unterschiedlichen Cache-Expiry-Zeit.</p>
<p>Als letztes definiere ich noch, dass alles, was bisher nicht gemappt werden konnte an das Skript &#8220;<em>/confession/controller.py</em>&#8221; delegiert werden soll.</p>
<h2>Und jetzt ins Internet damit&#8230;</h2>
<p>Um das ganze wirklich ins Netz zu bringen, muss man sich mit einem Google-Account auf der Seite anmelden und bekommt dann einen Freischaltcode per SMS zugeschickt. Damit kann man dann bis zu 10 Google-App-Engine-Applikationen hosten lassen. Im Web-Interface muss ich jetzt zunächst eine Applikation mit dem gleichen Label wie in der Konfiguration unter &#8220;application&#8221; angegeben anlegen:</p>
<p><img class="alignnone size-full wp-image-959" title="Applikation in der Google App Engine anlegen" src="http://www.zustandsforschung.de/wp-content/uploads/2009/08/create_application_app_engine.jpg" alt="Applikation in der Google App Engine anlegen" width="500" height="287" /></p>
<p>Da diese Applikations-Ids über die gesamte Google-App-Engine eindeutig sind, muss man unter Umständen ein bisschen rumprobieren, bis man einen Namen gefunden hat, der noch verfügbar ist.</p>
<p>Der Upload der Applikation erfolgt dann über ein mitgeliefertes Python-Skript:</p>
<pre>	python appcfg.py update
<pfad -zur-applikation></pfad></pre>
<p>Man wird noch nach Mailadresse und Passwort gefragt und kann anschließend sein Werk im Internet bewundern.</p>
<p>Wenn man jetzt noch ein bisschen Styling und eine nette Hintergrundgrafik hinzufügt, erhält man bereits ein nettes kleines Mashup. Ich habe der ganzen Sache noch ein paar nette Animationseffekte spendiert und im Endergebnis sieht das ganze dann folgendermaßen aus:</p>
<p><a href="http://confessiontweets.appspot.com/"><img class="alignnone size-full wp-image-820" title="ConfessionTweets" src="http://www.zustandsforschung.de/wp-content/uploads/2009/06/confession-tweets.jpg" alt="ConfessionTweets" width="500" height="607" /></a></p>
<h2>Fazit</h2>
<p>Natürlich hat man bei Verwendung der Google-App-Engine einen gewissen Vendor-Lock-In-Effekt, vor allem durch die Verwendung des Datastore statt einer Datenbank. Auf der anderen Seite bietet die GAE eine interessante Möglichkeit, mit einer neuen Idee schnell und günstig &#8211; weil kostenlos &#8211; Live zu gehen und erst wenn die Applikation ein gewisses Maß an Usern erreicht hat muss man sich über Rentabilität überhaupt Gedanken machen.</p>
<p>Außer dem hier beschriebenen Mini-Mashup habe ich noch meine Video-Verwaltungsapplikation &#8220;<a href="http://www.veedemus.com">veedemus &#8211; movie collection</a>&#8221; mit der Google App Engine implementiert. Dort kann man schon eher sehen, dass nicht nur Spielereien, sondern auch komplexe Applikationen mit der GAE umsetzbar sind.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.zustandsforschung.de/index.php/programmieren-mit-der-google-app-engine/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
	</channel>
</rss>

