<?xml version="1.0" encoding="UTF-8"?>
<feed
  xmlns="http://www.w3.org/2005/Atom"
  xmlns:thr="http://purl.org/syndication/thread/1.0"
  xml:lang="en"
   >
  <title type="text">Death of a Gremmie</title>
  <subtitle type="text">Brian Neal's blog about programming.</subtitle>

  <updated>2012-01-21T05:35:56Z</updated>
  <generator uri="http://blogofile.com/">Blogofile</generator>

  <link rel="alternate" type="text/html" href="http://deathofagremmie.com" />
  <id>http://deathofagremmie.com/feed/atom/</id>
  <link rel="self" type="application/atom+xml" href="http://deathofagremmie.com/feed/atom/" />
  <entry>
    <author>
      <name>Brian Neal</name>
      <uri>http://deathofagremmie.com</uri>
    </author>
    <title type="html"><![CDATA[A TeamSpeak 3 viewer with Python & Javascript]]></title>
    <link rel="alternate" type="text/html" href="http://deathofagremmie.com/2012/01/20/a-teamspeak-3-viewer-with-python-javascript" />
    <id>http://deathofagremmie.com/2012/01/20/a-teamspeak-3-viewer-with-python-javascript</id>
    <updated>2012-01-20T19:15:00Z</updated>
    <published>2012-01-20T19:15:00Z</published>
    <category scheme="http://deathofagremmie.com" term="Python" />
    <category scheme="http://deathofagremmie.com" term="Javascript" />
    <category scheme="http://deathofagremmie.com" term="TeamSpeak" />
    <summary type="html"><![CDATA[A TeamSpeak 3 viewer with Python & Javascript]]></summary>
    <content type="html" xml:base="http://deathofagremmie.com/2012/01/20/a-teamspeak-3-viewer-with-python-javascript"><![CDATA[<div class="document">
<div class="section" id="the-problem">
<h3>The Problem</h3>
<p>My gaming clan started using <a class="reference external" href="http://teamspeak.com/?page=teamspeak3">TeamSpeak 3</a> (TS3) for voice communications, so
it wasn't long before we wanted to see who was on the TS3 server from the clan's
server status page. Long ago, before I met Python, I had built the clan a server
status page in PHP. This consisted of cobbling together various home-made and
3rd party PHP scripts for querying game servers (Call of Duty, Battlefield) and
voice servers (TeamSpeak 2 and Mumble). But TeamSpeak 3 was a new one for us,
and I didn't have anything to query that. My interests in PHP are long behind
me, but we needed to add a TS3 viewer to the PHP page. The gaming clan's web
hosting is pretty vanilla; in other words PHP is the first class citizen. If I
really wanted to host a Python app I probably could have resorted to Fast CGI or
something. But I had no experience in that and no desire to go that way.</p>
<p>I briefly thought about finding a 3rd party PHP library to query a TS3 server.
The libraries are out there, but they are as you might expect: overly
complicated and/or pretty amateurish (no public source code repository). I even
considered writing my own PHP code to do the job, so I started looking for any
documentation on the TS3 server query protocol. Luckily, there is a <a class="reference external" href="http://media.teamspeak.com/ts3_literature/TeamSpeak%203%20Server%20Query%20Manual.pdf">TS3
query protocol document</a>, and it is fairly decent.</p>
<p>But, I just could not bring myself to write PHP again. On top of this, the
gaming clan's shared hosting blocks non-standard ports. If I did have a PHP
solution, the outgoing query to the TS3 server would have been blocked by the
host's firewall. It is a hassle to contact their technical support and try to
find a person who knows what a port is and get it unblocked (we've had to do
this over and over as each game comes out). Thus it ultimately boiled down to me
wanting to do this in Python. For me, life is too short to write PHP scripts.</p>
<p>I started thinking about writing a query application in Python using my
dedicated server that I use to host a few <a class="reference external" href="https://www.djangoproject.org">Django</a> powered websites. At first I
thought I'd generate the server status HTML on my server and display it in an
<tt class="docutils literal">&lt;iframe&gt;</tt> on the gaming clan's server. But then it hit me that all I really
needed to do is have my <a class="reference external" href="https://www.djangoproject.org">Django</a> application output a representation of the TS3
server status in <a class="reference external" href="http://json.org">JSON</a>, and then perhaps I could find a slick <a class="reference external" href="http://jquery.org">jQuery</a> tree menu
to display the status graphically. I really liked this idea, so here is a post
about the twists and turns I took implementing it.</p>
</div>
<div class="section" id="the-javascript">
<h3>The Javascript</h3>
<p>My searching turned up several jQuery tree menu plugins, but in the end I
settled on <a class="reference external" href="http://code.google.com/p/dynatree/">dynatree</a>. Dynatree had clear documentation I could understand, it
seems to be actively maintained, and it can generate a menu from JSON. After one
evening of reading the docs, I built a static test HTML page that could display
a tree menu built from JSON. Here the Javascript code I put in the test page's
<tt class="docutils literal">&lt;head&gt;</tt> section:</p>
<div class="highlight"><pre><span class="kd">var</span> <span class="nx">ts3_data</span> <span class="o">=</span> <span class="p">[</span>
   <span class="p">{</span><span class="nx">title</span><span class="o">:</span> <span class="s2">&quot;Phantom Aces&quot;</span><span class="p">,</span> <span class="nx">isFolder</span><span class="o">:</span> <span class="kc">true</span><span class="p">,</span> <span class="nx">expand</span><span class="o">:</span> <span class="kc">true</span><span class="p">,</span>
      <span class="nx">children</span><span class="o">:</span> <span class="p">[</span>
         <span class="p">{</span><span class="nx">title</span><span class="o">:</span> <span class="s2">&quot;MW3&quot;</span><span class="p">,</span> <span class="nx">isFolder</span><span class="o">:</span> <span class="kc">true</span><span class="p">,</span> <span class="nx">expand</span><span class="o">:</span> <span class="kc">true</span><span class="p">,</span>
            <span class="nx">children</span><span class="o">:</span> <span class="p">[</span>
               <span class="p">{</span><span class="nx">title</span><span class="o">:</span> <span class="s2">&quot;Hogan&quot;</span><span class="p">,</span> <span class="nx">icon</span><span class="o">:</span> <span class="s2">&quot;client.png&quot;</span><span class="p">},</span>
               <span class="p">{</span><span class="nx">title</span><span class="o">:</span> <span class="s2">&quot;Fritz!!&quot;</span><span class="p">,</span> <span class="nx">icon</span><span class="o">:</span> <span class="s2">&quot;client.png&quot;</span><span class="p">}</span>
            <span class="p">]</span>
         <span class="p">},</span>
         <span class="p">{</span><span class="nx">title</span><span class="o">:</span> <span class="s2">&quot;COD4&quot;</span><span class="p">,</span> <span class="nx">isFolder</span><span class="o">:</span> <span class="kc">true</span><span class="p">,</span> <span class="nx">expand</span><span class="o">:</span> <span class="kc">true</span><span class="p">,</span>
            <span class="nx">children</span><span class="o">:</span> <span class="p">[</span>
               <span class="p">{</span><span class="nx">title</span><span class="o">:</span> <span class="s2">&quot;Klink&quot;</span><span class="p">,</span> <span class="nx">icon</span><span class="o">:</span> <span class="s2">&quot;client.png&quot;</span><span class="p">}</span>
            <span class="p">]</span>
         <span class="p">},</span>
         <span class="p">{</span><span class="nx">title</span><span class="o">:</span> <span class="s2">&quot;Away&quot;</span><span class="p">,</span> <span class="nx">isFolder</span><span class="o">:</span> <span class="kc">true</span><span class="p">,</span> <span class="nx">children</span><span class="o">:</span> <span class="p">[],</span> <span class="nx">expand</span><span class="o">:</span> <span class="kc">true</span><span class="p">}</span>
     <span class="p">]</span>
   <span class="p">}</span>
<span class="p">];</span>

<span class="nx">$</span><span class="p">(</span><span class="kd">function</span><span class="p">(){</span>
   <span class="nx">$</span><span class="p">(</span><span class="s2">&quot;#ts3-tree&quot;</span><span class="p">).</span><span class="nx">dynatree</span><span class="p">({</span>
      <span class="nx">persist</span><span class="o">:</span> <span class="kc">false</span><span class="p">,</span>
      <span class="nx">children</span><span class="o">:</span> <span class="nx">ts3_data</span>
   <span class="p">});</span>
 <span class="p">});</span>
</pre></div>
<p>Note that <tt class="docutils literal">client.png</tt> is a small icon I found that I use in place of
dynatree's default file icon to represent TS3 clients. If I omitted the icon
attribute, the TS3 client would have appeared as a small file icon. Channels
appear as folder icons, and this didn't seem to unreasonable to me. In other
words I had no idea what a channel icon would look like. A folder was fine.</p>
<p>With dynatree, you don't need a lot of HTML markup, it does all the heavy
lifting. You simply have to give it an empty <tt class="docutils literal">&lt;div&gt;</tt> tag it can render
into.</p>
<div class="highlight"><pre><span class="nt">&lt;body&gt;</span>
   <span class="nt">&lt;div</span> <span class="na">id=</span><span class="s">&quot;ts3-tree&quot;</span><span class="nt">&gt;&lt;/div&gt;</span>
<span class="nt">&lt;/body&gt;</span>
<span class="nt">&lt;/html&gt;</span>
</pre></div>
<p>Here is a screenshot of the static test page in action.</p>
<img alt="/images/011-tree1.png" src="/images/011-tree1.png" />
<p>Nice! Thanks dynatree! Now all I need to do is figure out how to dynamically
generate the JSON data and get it into the gaming clan's server status page.</p>
</div>
<div class="section" id="the-python">
<h3>The Python</h3>
<p>Looking through the <a class="reference external" href="http://media.teamspeak.com/ts3_literature/TeamSpeak%203%20Server%20Query%20Manual.pdf">TS3 protocol documentation</a> I was somewhat surprised to
see that TS3 used the Telnet protocol for queries. So from my trusty shell I
telnet'ed into the TS3 server and played with the available commands. I made
notes on what commands I needed to issue to build my status display.</p>
<p>My experiments worked, and I could see a path forward, but there were still some
kinks to be worked out with the TS3 protocol. The data it sent back was escaped
in a strange way for one thing. I would have to post-process the data in Python
before I could use it. I didn't want to reinvent the wheel, so I did a quick
search for Python libraries for working with TS3. I found a few, but quickly
settled on Andrew William's <a class="reference external" href="http://pypi.python.org/pypi/python-ts3/0.1">python-ts3</a> library. It was small, easy to
understand, had tests, and a GitHub page. Perfect.</p>
<p>One of the great things about Python, of course, is the interactive shell. Armed
with the <a class="reference external" href="http://media.teamspeak.com/ts3_literature/TeamSpeak%203%20Server%20Query%20Manual.pdf">TS3 protocol documentation</a>, <a class="reference external" href="http://pypi.python.org/pypi/python-ts3/0.1">python-ts3</a>, and the Python shell, I was
able to interactively connect to the TS3 server and poke around again. This time
I was sitting above telnet using <a class="reference external" href="http://pypi.python.org/pypi/python-ts3/0.1">python-ts3</a> and I confirmed it would do the job
for me.</p>
<p>Another evening was spent coding up a Django view to query the TS3 server using
<a class="reference external" href="http://pypi.python.org/pypi/python-ts3/0.1">python-ts3</a> and to output the channel status as JSON.</p>
<div class="highlight"><pre><span class="kn">from</span> <span class="nn">django.conf</span> <span class="kn">import</span> <span class="n">settings</span>
<span class="kn">from</span> <span class="nn">django.core.cache</span> <span class="kn">import</span> <span class="n">cache</span>
<span class="kn">from</span> <span class="nn">django.http</span> <span class="kn">import</span> <span class="n">HttpResponse</span><span class="p">,</span> <span class="n">HttpResponseServerError</span>
<span class="kn">from</span> <span class="nn">django.utils</span> <span class="kn">import</span> <span class="n">simplejson</span>
<span class="kn">import</span> <span class="nn">ts3</span>

<span class="n">CACHE_KEY</span> <span class="o">=</span> <span class="s">&#39;ts3-json&#39;</span>
<span class="n">CACHE_TIMEOUT</span> <span class="o">=</span> <span class="mi">2</span> <span class="o">*</span> <span class="mi">60</span>

<span class="k">def</span> <span class="nf">ts3_query</span><span class="p">(</span><span class="n">request</span><span class="p">):</span>
    <span class="sd">&quot;&quot;&quot;</span>
<span class="sd">    Query the TeamSpeak3 server for status, and output a JSON</span>
<span class="sd">    representation.</span>

<span class="sd">    The JSON we return is targeted towards the jQuery plugin Dynatree</span>
<span class="sd">    http://code.google.com/p/dynatree/</span>

<span class="sd">    &quot;&quot;&quot;</span>
    <span class="c"># Do we have the result cached?</span>
    <span class="n">result</span> <span class="o">=</span> <span class="n">cache</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">CACHE_KEY</span><span class="p">)</span>
    <span class="k">if</span> <span class="n">result</span><span class="p">:</span>
        <span class="k">return</span> <span class="n">HttpResponse</span><span class="p">(</span><span class="n">result</span><span class="p">,</span> <span class="n">content_type</span><span class="o">=</span><span class="s">&#39;application/json&#39;</span><span class="p">)</span>

    <span class="c"># Cache miss, go query the remote server</span>

    <span class="k">try</span><span class="p">:</span>
        <span class="n">svr</span> <span class="o">=</span> <span class="n">ts3</span><span class="o">.</span><span class="n">TS3Server</span><span class="p">(</span><span class="n">settings</span><span class="o">.</span><span class="n">TS3_IP</span><span class="p">,</span> <span class="n">settings</span><span class="o">.</span><span class="n">TS3_PORT</span><span class="p">,</span>
                <span class="n">settings</span><span class="o">.</span><span class="n">TS3_VID</span><span class="p">)</span>
    <span class="k">except</span> <span class="n">ts3</span><span class="o">.</span><span class="n">ConnectionError</span><span class="p">:</span>
        <span class="k">return</span> <span class="n">HttpResponseServerError</span><span class="p">()</span>

    <span class="n">response</span> <span class="o">=</span> <span class="n">svr</span><span class="o">.</span><span class="n">send_command</span><span class="p">(</span><span class="s">&#39;serverinfo&#39;</span><span class="p">)</span>
    <span class="k">if</span> <span class="n">response</span><span class="o">.</span><span class="n">response</span><span class="p">[</span><span class="s">&#39;msg&#39;</span><span class="p">]</span> <span class="o">!=</span> <span class="s">&#39;ok&#39;</span><span class="p">:</span>
        <span class="k">return</span> <span class="n">HttpResponseServerError</span><span class="p">()</span>
    <span class="n">svr_info</span> <span class="o">=</span> <span class="n">response</span><span class="o">.</span><span class="n">data</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span>

    <span class="n">response</span> <span class="o">=</span> <span class="n">svr</span><span class="o">.</span><span class="n">send_command</span><span class="p">(</span><span class="s">&#39;channellist&#39;</span><span class="p">)</span>
    <span class="k">if</span> <span class="n">response</span><span class="o">.</span><span class="n">response</span><span class="p">[</span><span class="s">&#39;msg&#39;</span><span class="p">]</span> <span class="o">!=</span> <span class="s">&#39;ok&#39;</span><span class="p">:</span>
        <span class="k">return</span> <span class="n">HttpResponseServerError</span><span class="p">()</span>
    <span class="n">channel_list</span> <span class="o">=</span> <span class="n">response</span><span class="o">.</span><span class="n">data</span>

    <span class="n">response</span> <span class="o">=</span> <span class="n">svr</span><span class="o">.</span><span class="n">send_command</span><span class="p">(</span><span class="s">&#39;clientlist&#39;</span><span class="p">)</span>
    <span class="k">if</span> <span class="n">response</span><span class="o">.</span><span class="n">response</span><span class="p">[</span><span class="s">&#39;msg&#39;</span><span class="p">]</span> <span class="o">!=</span> <span class="s">&#39;ok&#39;</span><span class="p">:</span>
        <span class="k">return</span> <span class="n">HttpResponseServerError</span><span class="p">()</span>
    <span class="n">client_list</span> <span class="o">=</span> <span class="n">response</span><span class="o">.</span><span class="n">data</span>

    <span class="c"># Start building the channel / client tree.</span>
    <span class="c"># We save tree nodes in a dictionary, keyed by their id so we can find</span>
    <span class="c"># them later in order to support arbitrary channel hierarchies.</span>
    <span class="n">channels</span> <span class="o">=</span> <span class="p">{}</span>

    <span class="c"># Build the root, or channel 0</span>
    <span class="n">channels</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">=</span> <span class="p">{</span>
        <span class="s">&#39;title&#39;</span><span class="p">:</span> <span class="n">svr_info</span><span class="p">[</span><span class="s">&#39;virtualserver_name&#39;</span><span class="p">],</span>
        <span class="s">&#39;isFolder&#39;</span><span class="p">:</span> <span class="bp">True</span><span class="p">,</span>
        <span class="s">&#39;expand&#39;</span><span class="p">:</span> <span class="bp">True</span><span class="p">,</span>
        <span class="s">&#39;children&#39;</span><span class="p">:</span> <span class="p">[]</span>
    <span class="p">}</span>

    <span class="c"># Add the channels to our tree</span>

    <span class="k">for</span> <span class="n">channel</span> <span class="ow">in</span> <span class="n">channel_list</span><span class="p">:</span>
        <span class="n">node</span> <span class="o">=</span> <span class="p">{</span>
            <span class="s">&#39;title&#39;</span><span class="p">:</span> <span class="n">channel</span><span class="p">[</span><span class="s">&#39;channel_name&#39;</span><span class="p">],</span>
            <span class="s">&#39;isFolder&#39;</span><span class="p">:</span> <span class="bp">True</span><span class="p">,</span>
            <span class="s">&#39;expand&#39;</span><span class="p">:</span> <span class="bp">True</span><span class="p">,</span>
            <span class="s">&#39;children&#39;</span><span class="p">:</span> <span class="p">[]</span>
        <span class="p">}</span>
        <span class="n">parent</span> <span class="o">=</span> <span class="n">channels</span><span class="p">[</span><span class="nb">int</span><span class="p">(</span><span class="n">channel</span><span class="p">[</span><span class="s">&#39;pid&#39;</span><span class="p">])]</span>
        <span class="n">parent</span><span class="p">[</span><span class="s">&#39;children&#39;</span><span class="p">]</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">node</span><span class="p">)</span>
        <span class="n">channels</span><span class="p">[</span><span class="nb">int</span><span class="p">(</span><span class="n">channel</span><span class="p">[</span><span class="s">&#39;cid&#39;</span><span class="p">])]</span> <span class="o">=</span> <span class="n">node</span>

    <span class="c"># Add the clients to the tree</span>

    <span class="k">for</span> <span class="n">client</span> <span class="ow">in</span> <span class="n">client_list</span><span class="p">:</span>
        <span class="k">if</span> <span class="n">client</span><span class="p">[</span><span class="s">&#39;client_type&#39;</span><span class="p">]</span> <span class="o">==</span> <span class="s">&#39;0&#39;</span><span class="p">:</span>
            <span class="n">node</span> <span class="o">=</span> <span class="p">{</span>
                <span class="s">&#39;title&#39;</span><span class="p">:</span> <span class="n">client</span><span class="p">[</span><span class="s">&#39;client_nickname&#39;</span><span class="p">],</span>
                <span class="s">&#39;icon&#39;</span><span class="p">:</span> <span class="s">&#39;client.png&#39;</span>
            <span class="p">}</span>
            <span class="n">channel</span> <span class="o">=</span> <span class="n">channels</span><span class="p">[</span><span class="nb">int</span><span class="p">(</span><span class="n">client</span><span class="p">[</span><span class="s">&#39;cid&#39;</span><span class="p">])]</span>
            <span class="n">channel</span><span class="p">[</span><span class="s">&#39;children&#39;</span><span class="p">]</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">node</span><span class="p">)</span>

    <span class="n">tree</span> <span class="o">=</span> <span class="p">[</span><span class="n">channels</span><span class="p">[</span><span class="mi">0</span><span class="p">]]</span>

    <span class="c"># convert to JSON</span>
    <span class="n">json</span> <span class="o">=</span> <span class="n">simplejson</span><span class="o">.</span><span class="n">dumps</span><span class="p">(</span><span class="n">tree</span><span class="p">)</span>

    <span class="n">cache</span><span class="o">.</span><span class="n">set</span><span class="p">(</span><span class="n">CACHE_KEY</span><span class="p">,</span> <span class="n">json</span><span class="p">,</span> <span class="n">CACHE_TIMEOUT</span><span class="p">)</span>

    <span class="k">return</span> <span class="n">HttpResponse</span><span class="p">(</span><span class="n">json</span><span class="p">,</span> <span class="n">content_type</span><span class="o">=</span><span class="s">&#39;application/json&#39;</span><span class="p">)</span>
</pre></div>
<p>I have to make three queries to the TS3 server to get all the information I
need. The <tt class="docutils literal">serverinfo</tt> command is issued to retrieve the TS3 virtual server's
name. The <tt class="docutils literal">channellist</tt> command retrieves the list of channels. The
<tt class="docutils literal">clientlist</tt> command gets the list of TS3 clients that are currently
connected. For more information on these three commands see the TS3 query
protocol document.</p>
<p>The only real tricky part of this code was figuring out how to represent an
arbitrary, deeply-nested channel tree in Python. I ended up guessing that
<tt class="docutils literal">cid</tt> meant channel ID and <tt class="docutils literal">pid</tt> meant parent ID in the TS3 query data. I
squirrel away the channels in a <tt class="docutils literal">channels</tt> dictionary, keyed by channel ID.
The root channel has an ID of 0. While iterating over the channel list, I can
retrieve the parent channel from the <tt class="docutils literal">channels</tt> dictionary by ID and append
the new channel to the parent's <tt class="docutils literal">children</tt> list. Clients are handled the same
way, but have different attributes. By inspecting the <tt class="docutils literal">clientlist</tt> data in the
Python shell, I noticed that my Telnet client also showed up in that list.
However it had a <tt class="docutils literal">client_type</tt> of 1, whereas the normal PC clients had a
<tt class="docutils literal">client_type</tt> of 0.</p>
<p>I decided to cache the results for 2 minutes to reduce hits on the TS3 server,
as it has flood protection. This probably isn't needed given the size of our
gaming clan, but Django makes it easy to do, so why not?</p>
</div>
<div class="section" id="putting-it-all-together">
<h3>Putting it all together</h3>
<p>At this point I knew how to use my Django application to query the TS3 server
and build status in JSON format. I also knew what the Javascript and HTML on the
gaming clan's server status page (written in PHP) had to look like to render
that JSON status.</p>
<p>The problem was the server status page was on one server, and my Django
application was on another. At first I thought it would be no problem for the
Javascript to do a <tt class="docutils literal">GET</tt> on my Django server and retrieve the JSON. However I
had some vague memory of the browser security model, and after some googling I
was reminded of the <a class="reference external" href="http://en.wikipedia.org/wiki/Same_origin_policy">same origin policy</a>. Rats. That wasn't going to work.</p>
<p>I briefly researched <a class="reference external" href="http://en.wikipedia.org/wiki/JSONP">JSONP</a>, which is the technique that Facebook &amp; Google use
to embed those little &quot;like&quot; and &quot;+1&quot; buttons on your web pages. But in the end
it was just as easy to have the PHP script make the <tt class="docutils literal">GET</tt> request to my Django
application using a <a class="reference external" href="http://php.net/manual/en/function.file-get-contents.php">file_get_contents()</a> call. The PHP can then embed the JSON
directly into the server status page:</p>
<div class="highlight"><pre><span class="x">$ts3_source = &#39;http://example.com/ts3/&#39;;</span>
<span class="x">$ts3_json = file_get_contents($ts3_source);</span>

<span class="x">require_once &#39;header.php&#39;;</span>
</pre></div>
<p>And in header.php, some HTML sprinkled with some PHP:</p>
<div class="highlight"><pre><span class="nt">&lt;script </span><span class="na">type=</span><span class="s">&quot;text/javascript&quot;</span><span class="nt">&gt;</span>
   <span class="kd">var</span> <span class="nx">ts3_data</span> <span class="o">=</span> <span class="o">&lt;?</span><span class="nx">php</span> <span class="nx">echo</span> <span class="nx">$ts3_json</span><span class="p">;</span> <span class="o">?&gt;</span><span class="p">;</span>

   <span class="nx">$</span><span class="p">(</span><span class="kd">function</span><span class="p">(){</span>
      <span class="nx">$</span><span class="p">(</span><span class="s2">&quot;#ts3-tree&quot;</span><span class="p">).</span><span class="nx">dynatree</span><span class="p">({</span>
         <span class="nx">persist</span><span class="o">:</span> <span class="kc">false</span><span class="p">,</span>
         <span class="nx">children</span><span class="o">:</span> <span class="nx">ts3_data</span>
      <span class="p">});</span>
    <span class="p">});</span>
<span class="nt">&lt;/script&gt;</span>
</pre></div>
<p>That did the trick. In the end I had to touch a little PHP, but it was
tolerable.  That was a very round-about solution to building a TS3 viewer in
Python and Javascript. While I doubt you will have the same strange requirements
that I had (multiple servers), I hope you can see how to combine a few
technologies to make a TS3 viewer in Python.</p>
</div>
</div>
]]></content>
  </entry>
  <entry>
    <author>
      <name>Brian Neal</name>
      <uri>http://deathofagremmie.com</uri>
    </author>
    <title type="html"><![CDATA[Who's Online with Redis & Python, a slight return]]></title>
    <link rel="alternate" type="text/html" href="http://deathofagremmie.com/2011/12/17/who-s-online-with-redis-python-a-slight-return" />
    <id>http://deathofagremmie.com/2011/12/17/who-s-online-with-redis-python-a-slight-return</id>
    <updated>2011-12-17T19:05:00Z</updated>
    <published>2011-12-17T19:05:00Z</published>
    <category scheme="http://deathofagremmie.com" term="Python" />
    <category scheme="http://deathofagremmie.com" term="Redis" />
    <summary type="html"><![CDATA[Who's Online with Redis & Python, a slight return]]></summary>
    <content type="html" xml:base="http://deathofagremmie.com/2011/12/17/who-s-online-with-redis-python-a-slight-return"><![CDATA[<div class="document">
<p>In a <a class="reference external" href="http://deathofagremmie.com/2011/04/25/a-better-who-s-online-with-redis-python/">previous post</a>, I blogged about building a &quot;Who's Online&quot; feature using
<a class="reference external" href="http://redis.io/">Redis</a> and <a class="reference external" href="http://www.python.org">Python</a> with <a class="reference external" href="https://github.com/andymccurdy/redis-py">redis-py</a>.  I've been integrating <a class="reference external" href="http://celeryproject.org">Celery</a> into my
website, and I stumbled across this old code. Since I made that post, I
discovered yet another cool feature in Redis: sorted sets. So here is an even
better way of implementing this feature using Redis sorted sets.</p>
<p>A sorted set in Redis is like a regular set, but each member has a numeric
score. When you add a member to a sorted set, you also specify the score for
that member. You can then retrieve set members if their score falls into a
certain range. You can also easily remove members outside a given score range.</p>
<p>For a &quot;Who's Online&quot; feature, we need a sorted set to represent the set
of all users online. Whenever we see a user, we insert that user into the set
along with the current time as their score. This is accomplished with the Redis
<a class="reference external" href="http://redis.io/commands/zadd">zadd</a> command.  If the user is already in the set, <a class="reference external" href="http://redis.io/commands/zadd">zadd</a> simply updates
their score with the current time.</p>
<p>To obtain the curret list of who's online, we use the <a class="reference external" href="http://redis.io/commands/zrangebyscore">zrangebyscore</a> command to
retrieve the list of users who's score (time) lies between, say, 15 minutes ago,
until now.</p>
<p>Periodically, we need to remove stale members from the set. This can be
accomplished by using the <a class="reference external" href="http://redis.io/commands/zremrangebyscore">zremrangebyscore</a> command. This command will remove
all members that have a score between minimum and maximum values. In this case,
we can use the beginning of time for the minimum, and 15 minutes ago for the
maximum.</p>
<p>That's really it in a nutshell. This is much simpler than my previous
solution which used two sets.</p>
<p>So let's look at some code. The first problem we need to solve is how to
convert a Python <tt class="docutils literal">datetime</tt> object into a score. This can be accomplished by
converting the <tt class="docutils literal">datetime</tt> into a POSIX timestamp integer, which is the number
of seconds from the UNIX epoch of January 1, 1970.</p>
<div class="highlight"><pre><span class="kn">import</span> <span class="nn">datetime</span>
<span class="kn">import</span> <span class="nn">time</span>

<span class="k">def</span> <span class="nf">to_timestamp</span><span class="p">(</span><span class="n">dt</span><span class="p">):</span>
    <span class="sd">&quot;&quot;&quot;</span>
<span class="sd">    Turn the supplied datetime object into a UNIX timestamp integer.</span>

<span class="sd">    &quot;&quot;&quot;</span>
    <span class="k">return</span> <span class="nb">int</span><span class="p">(</span><span class="n">time</span><span class="o">.</span><span class="n">mktime</span><span class="p">(</span><span class="n">dt</span><span class="o">.</span><span class="n">timetuple</span><span class="p">()))</span>
</pre></div>
<p>With that handy function, here are some examples of the operations described
above.</p>
<div class="highlight"><pre><span class="kn">import</span> <span class="nn">redis</span>

<span class="c"># Redis set keys:</span>
<span class="n">USER_SET_KEY</span> <span class="o">=</span> <span class="s">&quot;whos_online:users&quot;</span>

<span class="c"># the period over which we collect who&#39;s online stats:</span>
<span class="n">MAX_AGE</span> <span class="o">=</span> <span class="n">datetime</span><span class="o">.</span><span class="n">timedelta</span><span class="p">(</span><span class="n">minutes</span><span class="o">=</span><span class="mi">15</span><span class="p">)</span>

<span class="c"># obtain a connection to redis:</span>
<span class="n">conn</span> <span class="o">=</span> <span class="n">redis</span><span class="o">.</span><span class="n">StrictRedis</span><span class="p">()</span>

<span class="c"># add/update a user to the who&#39;s online set:</span>

<span class="n">username</span> <span class="o">=</span> <span class="s">&quot;sally&quot;</span>
<span class="n">ts</span> <span class="o">=</span> <span class="n">to_timestamp</span><span class="p">(</span><span class="n">datetime</span><span class="o">.</span><span class="n">datetime</span><span class="o">.</span><span class="n">now</span><span class="p">())</span>
<span class="n">conn</span><span class="o">.</span><span class="n">zadd</span><span class="p">(</span><span class="n">USER_SET_KEY</span><span class="p">,</span> <span class="n">ts</span><span class="p">,</span> <span class="n">username</span><span class="p">)</span>

<span class="c"># retrieve the list of users who have been active in the last MAX_AGE minutes</span>

<span class="n">now</span> <span class="o">=</span> <span class="n">datetime</span><span class="o">.</span><span class="n">datetime</span><span class="o">.</span><span class="n">now</span><span class="p">()</span>
<span class="nb">min</span> <span class="o">=</span> <span class="n">to_timestamp</span><span class="p">(</span><span class="n">now</span> <span class="o">-</span> <span class="n">MAX_AGE</span><span class="p">)</span>
<span class="nb">max</span> <span class="o">=</span> <span class="n">to_timestamp</span><span class="p">(</span><span class="n">now</span><span class="p">)</span>

<span class="n">whos_online</span> <span class="o">=</span> <span class="n">conn</span><span class="o">.</span><span class="n">zrangebyscore</span><span class="p">(</span><span class="n">USER_SET_KEY</span><span class="p">,</span> <span class="nb">min</span><span class="p">,</span> <span class="nb">max</span><span class="p">)</span>

<span class="c"># e.g. whos_online = [&#39;sally&#39;, &#39;harry&#39;, &#39;joe&#39;]</span>

<span class="c"># periodically remove stale members</span>

<span class="n">cutoff</span> <span class="o">=</span> <span class="n">to_timestamp</span><span class="p">(</span><span class="n">datetime</span><span class="o">.</span><span class="n">datetime</span><span class="o">.</span><span class="n">now</span><span class="p">()</span> <span class="o">-</span> <span class="n">MAX_AGE</span><span class="p">)</span>
<span class="n">conn</span><span class="o">.</span><span class="n">zremrangebyscore</span><span class="p">(</span><span class="n">USER_SET_KEY</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="n">cutoff</span><span class="p">)</span>
</pre></div>
</div>
]]></content>
  </entry>
  <entry>
    <author>
      <name>Brian Neal</name>
      <uri>http://deathofagremmie.com</uri>
    </author>
    <title type="html"><![CDATA[Upgrading Trac on Windows Gotchas]]></title>
    <link rel="alternate" type="text/html" href="http://deathofagremmie.com/2011/09/12/upgrading-trac-on-windows-gotchas" />
    <id>http://deathofagremmie.com/2011/09/12/upgrading-trac-on-windows-gotchas</id>
    <updated>2011-09-12T22:15:00Z</updated>
    <published>2011-09-12T22:15:00Z</published>
    <category scheme="http://deathofagremmie.com" term="Python" />
    <category scheme="http://deathofagremmie.com" term="Subversion" />
    <category scheme="http://deathofagremmie.com" term="Windows" />
    <category scheme="http://deathofagremmie.com" term="Trac" />
    <summary type="html"><![CDATA[Upgrading Trac on Windows Gotchas]]></summary>
    <content type="html" xml:base="http://deathofagremmie.com/2011/09/12/upgrading-trac-on-windows-gotchas"><![CDATA[<div class="document">
<p>At work, we are outfitted with Windows servers. Despite this obstacle, I managed
to install <a class="reference external" href="http://trac.edgewall.org/">Trac</a> and <a class="reference external" href="http://subversion.apache.org/">Subversion</a> a few years ago. During a break in the action,
we decided to update Subversion (SVN) and Trac. Since we are on Windows, this
means we have to rely on the <a class="reference external" href="http://sourceforge.net/projects/win32svn/">kindness of strangers</a> for Subversion binaries. I
ran into a couple of gotchas I'd like to document here to help anyone else who
runs into these.</p>
<p>I managed to get Subversion and Trac up and running without any real problems.
However when Trac needed to access SVN to display changesets or a timeline, for
example, I got an error:</p>
<p><tt class="docutils literal">tracerror: unsupported version control system &quot;svn&quot; no module named _fs</tt></p>
<p>After some googling, I finally found that this issue is <a class="reference external" href="http://trac.edgewall.org/wiki/TracSubversion">documented on the Trac
wiki</a>, but it was kind of hard to find. To fix this problem, you have to rename
the Python SVN binding's DLLs to <tt class="docutils literal">*.pyd</tt>. Specifically, change the
<tt class="docutils literal"><span class="pre">libsvn/*.dll</span></tt> files to <tt class="docutils literal"><span class="pre">libsvn/*.pyd</span></tt>, but don't change the name of
<tt class="docutils literal"><span class="pre">libsvn_swig_py-1.dll</span></tt>. I'd really like to hear an explanation of why one
needs to do this. Why doesn't the Python-Windows build process do this for you?</p>
<p>The second problem I ran into dealt with <a class="reference external" href="http://code.google.com/p/modwsgi/">mod_wsgi</a> on Windows. Originally, a few
years ago, I setup Trac to run under <a class="reference external" href="http://www.modpython.org/">mod_python</a>. mod_python has long been
out of favor, so I decided to cut over to mod_wsgi. On my Linux boxes, I always
run mod_wsgi in daemon mode. When I tried to configure this on Windows, Apache
complained about an unknown directive <tt class="docutils literal">WSGIDaemonProcess</tt>. It turns out that
<a class="reference external" href="http://code.google.com/p/modwsgi/wiki/ConfigurationDirectives#WSGIDaemonProcess">this mode is not supported on Windows</a>. You'll have to use the embedded mode on
Windows.</p>
</div>
]]></content>
  </entry>
  <entry>
    <author>
      <name>Brian Neal</name>
      <uri>http://deathofagremmie.com</uri>
    </author>
    <title type="html"><![CDATA[Implementing OAuth using Google's Python Client Library]]></title>
    <link rel="alternate" type="text/html" href="http://deathofagremmie.com/2011/07/04/implementing-oauth-using-google-s-python-client-library" />
    <id>http://deathofagremmie.com/2011/07/04/implementing-oauth-using-google-s-python-client-library</id>
    <updated>2011-07-04T13:00:00Z</updated>
    <published>2011-07-04T13:00:00Z</published>
    <category scheme="http://deathofagremmie.com" term="Python" />
    <category scheme="http://deathofagremmie.com" term="OAuth" />
    <category scheme="http://deathofagremmie.com" term="GData" />
    <category scheme="http://deathofagremmie.com" term="Google" />
    <summary type="html"><![CDATA[Implementing OAuth using Google's Python Client Library]]></summary>
    <content type="html" xml:base="http://deathofagremmie.com/2011/07/04/implementing-oauth-using-google-s-python-client-library"><![CDATA[<div class="document">
<p>My <a class="reference external" href="http://djangoproject.com">Django</a> powered website allows users to submit events for a site calendar
that is built upon Google Calendar.  After an admin approves events, I use
Google's <a class="reference external" href="http://code.google.com/apis/calendar/data/2.0/developers_guide_python.html">Python Client Library</a> to add, delete, or update events on the Google
calendar associated with my personal Google account. I wrote this application a
few years ago, and it used the <a class="reference external" href="http://code.google.com/apis/calendar/data/2.0/developers_guide_python.html#AuthClientLogin">ClientLogin</a> method for authentication. I
recently decided to upgrade this to the <a class="reference external" href="http://code.google.com/apis/gdata/docs/auth/oauth.html">OAuth</a> authentication method. The
ClientLogin method isn't very secure and it doesn't play well with Google's
<a class="reference external" href="http://googleblog.blogspot.com/2011/02/advanced-sign-in-security-for-your.html">two-step verification</a>. After hearing about a friend who had his GMail account
compromised and all his email deleted I decided it was long past due to get
two-step verification on my account. But first I needed to upgrade my web
application to OAuth.</p>
<p>In this post I'll boil down the code I used to implement the elaborate OAuth
dance. It really isn't that much code, but the Google documentation is somewhat
confusing and scattered across a bewildering number of documents. I found at
least one error in the documentation that I will point out. Although I am using
Django, I will omit details specific to Django where I can.</p>
<p>In addition to switching from ClientLogin to OAuth, I also upgraded to version
2.0 of the Google Data API. This had more implications for my calendar-specific
code, and perhaps I can go over that in a future post.</p>
<div class="section" id="getting-started-and-registering-with-google">
<h3>Getting started and registering with Google</h3>
<p>To understand the basics of OAuth, I suggest you read <a class="reference external" href="http://code.google.com/apis/accounts/docs/OAuth.html">OAuth 1.0 for Web
Applications</a>. I decided to go for maximum security and use RSA-SHA1 signing on
all my requests to Google. This requires that I verify my domain and then
<a class="reference external" href="http://code.google.com/apis/accounts/docs/RegistrationForWebAppsAuto.html">register my application</a> with Google, which includes uploading a security
certificate. Google provides documentation that describes how you can <a class="reference external" href="http://code.google.com/apis/gdata/docs/auth/oauth.html#GeneratingKeyCert">create a
self-signing private key and certificate</a> using OpenSSL.</p>
</div>
<div class="section" id="fetching-a-request-token-and-authorizing-access">
<h3>Fetching a Request Token and authorizing access</h3>
<p>To perform the first part of the OAuth dance, you must ask Google for a request
token. When you make this request, you state the &quot;scope&quot; of your future work by
listing the Google resources you are going to access. In our case, this is the
calendar resources. You also provide a &quot;consumer key&quot; that Google assigned to
you when you registered your application. This allows Google to retrieve the
security certificate you previously uploaded when you registered. This is very
important because this request is going to be signed with your private key.
Fortunately the Python library takes care of all the signing details, you simply
must provide your private key in PEM format. And finally, you provide a
&quot;callback URL&quot; that Google will send your browser to after you (or your users)
have manually authorized this request.</p>
<p>Once you have received the request token from Google, you have to squirrel it
away somewhere, then redirect your (or your user's) browser to a Google
authorization page. Once the user has authorized your application, Google sends
the browser to the callback URL to continue the process. Here I show the
distilled code I used that asks for a request token, then sends the user to the
authorization page.</p>
<div class="highlight"><pre><span class="kn">import</span> <span class="nn">gdata.gauth</span>
<span class="kn">from</span> <span class="nn">gdata.calendar_resource.client</span> <span class="kn">import</span> <span class="n">CalendarResourceClient</span>

<span class="n">USER_AGENT</span> <span class="o">=</span> <span class="s">&#39;mydomain-myapp-v1&#39;</span> <span class="c"># my made up user agent string</span>

<span class="n">client</span> <span class="o">=</span> <span class="n">CalendarResourceClient</span><span class="p">(</span><span class="bp">None</span><span class="p">,</span> <span class="n">source</span><span class="o">=</span><span class="n">USER_AGENT</span><span class="p">)</span>

<span class="c"># obtain my private key that I saved previously on the filesystem:</span>
<span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="n">settings</span><span class="o">.</span><span class="n">GOOGLE_OAUTH_PRIVATE_KEY_PATH</span><span class="p">,</span> <span class="s">&#39;r&#39;</span><span class="p">)</span> <span class="k">as</span> <span class="n">f</span><span class="p">:</span>
    <span class="n">rsa_key</span> <span class="o">=</span> <span class="n">f</span><span class="o">.</span><span class="n">read</span><span class="p">()</span>

<span class="c"># Ask for a request token:</span>
<span class="c"># scopes - a list of scope strings that the request token is for. See</span>
<span class="c"># http://code.google.com/apis/gdata/faq.html#AuthScopes</span>
<span class="c"># callback_url - URL to send the user after authorizing our app</span>

<span class="n">scopes</span> <span class="o">=</span> <span class="p">[</span><span class="s">&#39;https://www.google.com/calendar/feeds/&#39;</span><span class="p">]</span>
<span class="n">callback_url</span> <span class="o">=</span> <span class="s">&#39;http://example.com/some/url/to/callback&#39;</span>

<span class="n">request_token</span> <span class="o">=</span> <span class="n">client</span><span class="o">.</span><span class="n">GetOAuthToken</span><span class="p">(</span>
        <span class="n">scopes</span><span class="p">,</span>
        <span class="n">callback_url</span><span class="p">,</span>
        <span class="n">settings</span><span class="o">.</span><span class="n">GOOGLE_OAUTH_CONSUMER_KEY</span><span class="p">,</span> <span class="c"># from the registration process</span>
        <span class="n">rsa_private_key</span><span class="o">=</span><span class="n">rsa_key</span><span class="p">)</span>

<span class="c"># Before redirecting, save the request token somewhere; here I place it in</span>
<span class="c"># the session (this line is Django specific):</span>
<span class="n">request</span><span class="o">.</span><span class="n">session</span><span class="p">[</span><span class="n">REQ_TOKEN_SESSION_KEY</span><span class="p">]</span> <span class="o">=</span> <span class="n">request_token</span>

<span class="c"># Generate the authorization URL.</span>
<span class="c"># Despite the documentation, don&#39;t do this:</span>
<span class="c">#    auth_url = request_token.generate_authorization_url(domain=None)</span>
<span class="c"># Do this instead if you are not using a Google Apps domain:</span>
<span class="n">auth_url</span> <span class="o">=</span> <span class="n">request_token</span><span class="o">.</span><span class="n">generate_authorization_url</span><span class="p">()</span>

<span class="c"># Now redirect the user somehow to the auth_url; here is how you might do</span>
<span class="c"># it in Django:</span>
<span class="k">return</span> <span class="n">HttpResponseRedirect</span><span class="p">(</span><span class="n">auth_url</span><span class="p">)</span>
</pre></div>
<p>A couple of notes on the above:</p>
<ul class="simple">
<li>You don't have to use <tt class="docutils literal">CalendarResourceClient</tt>, it just made the most sense
for me since I am doing calendar stuff later on. Any class that inherits from
<tt class="docutils literal">gdata.client.GDClient</tt> will work. You might be able to use that class
directly. Google uses <tt class="docutils literal">gdata.docs.client.DocsClient</tt> in their examples.</li>
<li>I chose to store my private key in a file rather than the database. If you do
so, it's probably a good idea to make the file readable only to the user your
webserver runs your application as.</li>
<li>After getting the request token you must save it somehow. You can save it in
the session, the database, or perhaps a file. Since this is only temporary, I
chose to save it in the session. The code I have here is Django specific.</li>
<li>When generating the authorization URL, don't pass in <tt class="docutils literal">domain=None</tt> if you
aren't using a Google Apps domain like the documentation states. This appears
to be an error in the documentation. Just omit it and let it use the default
value of <tt class="docutils literal">&quot;default&quot;</tt> (see the source code).</li>
<li>After using the request token to generate the authorization URL, redirect the
browser to it.</li>
</ul>
</div>
<div class="section" id="extracting-and-upgrading-to-an-access-token">
<h3>Extracting and upgrading to an Access Token</h3>
<p>The user will then be taken to a Google authorization page. The page will show the
user what parts of their Google account your application is trying to access
using the information you provided in the <tt class="docutils literal">scopes</tt> parameter. If the user
accepts, Google will then redirect the browser to your callback URL where we can
complete the process.</p>
<p>The code running at our callback URL must retrieve the request token that we
saved earlier, and combine that with certain <tt class="docutils literal">GET</tt> parameters Google attached
to our callback URL. This is all done for us by the Python library. We then send
this new token back to Google to upgrade it to an actual access token. If this
succeeds, we can then save this new access token in our database for use in
subsequent Google API operations. The access token is a Python object, so you
can serialize it use the pickle module, or use routines provided by Google
(shown below).</p>
<div class="highlight"><pre><span class="c"># Code running at our callback URL:</span>
<span class="c"># Retrieve the request token we saved earlier in our session</span>
<span class="n">saved_token</span> <span class="o">=</span> <span class="n">request</span><span class="o">.</span><span class="n">session</span><span class="p">[</span><span class="n">REQ_TOKEN_SESSION_KEY</span><span class="p">]</span>

<span class="c"># Authorize it by combining it with GET parameters received from Google</span>
<span class="n">request_token</span> <span class="o">=</span> <span class="n">gdata</span><span class="o">.</span><span class="n">gauth</span><span class="o">.</span><span class="n">AuthorizeRequestToken</span><span class="p">(</span><span class="n">saved_token</span><span class="p">,</span>
                    <span class="n">request</span><span class="o">.</span><span class="n">build_absolute_uri</span><span class="p">())</span>

<span class="c"># Upgrade it to an access token</span>
<span class="n">client</span> <span class="o">=</span> <span class="n">CalendarResourceClient</span><span class="p">(</span><span class="bp">None</span><span class="p">,</span> <span class="n">source</span><span class="o">=</span><span class="n">USER_AGENT</span><span class="p">)</span>
<span class="n">access_token</span> <span class="o">=</span> <span class="n">client</span><span class="o">.</span><span class="n">GetAccessToken</span><span class="p">(</span><span class="n">request_token</span><span class="p">)</span>

<span class="c"># Now save access_token somewhere, e.g. a database. So first serialize it:</span>
<span class="n">access_token_str</span> <span class="o">=</span>  <span class="n">gdata</span><span class="o">.</span><span class="n">gauth</span><span class="o">.</span><span class="n">TokenToBlob</span><span class="p">(</span><span class="n">access_token</span><span class="p">)</span>

<span class="c"># Save to database (details omitted)</span>
</pre></div>
<p>Some notes on the above code:</p>
<ul class="simple">
<li>Once called back, our code must retrieve the request token we saved in our
session. The code shown is specific to Django.</li>
<li>We then combine this saved request token with certain <tt class="docutils literal">GET</tt> parameters that
Google added to our callback URL. The <tt class="docutils literal">AuthorizeRequestToken</tt> function takes care of
those details for us. The second argument to that function requires the full URL
including <tt class="docutils literal">GET</tt> parameters as a string. Here I populate that argument by
using a Django-specific method of retrieving that information.</li>
<li>Finally, you upgrade your token to an access token by making one last call to
Google. You should now save a serialized version of this access token in your
database for future use.</li>
</ul>
</div>
<div class="section" id="using-your-shiny-new-access-token">
<h3>Using your shiny new Access Token</h3>
<p>Once you have saved your access token, you won't have to do this crazy dance
again until the token either expires, or the user revokes your application's
access to the Google account. To use it in a calendar operation, for example,
you simply retrieve it from your database, deserialize it, and then use it to
create a <tt class="docutils literal">CalendarClient</tt>.</p>
<div class="highlight"><pre><span class="kn">from</span> <span class="nn">gdata.calendar.client</span> <span class="kn">import</span> <span class="n">CalendarClient</span>

<span class="c"># retrieve access token from the database:</span>
<span class="n">access_token_str</span> <span class="o">=</span> <span class="o">...</span>
<span class="n">access_token</span> <span class="o">=</span> <span class="n">gdata</span><span class="o">.</span><span class="n">gauth</span><span class="o">.</span><span class="n">TokenFromBlob</span><span class="p">(</span><span class="n">access_token_str</span><span class="p">)</span>

<span class="n">client</span> <span class="o">=</span> <span class="n">CalendarClient</span><span class="p">(</span><span class="n">source</span><span class="o">=</span><span class="n">USER_AGENT</span><span class="p">,</span> <span class="n">auth_token</span><span class="o">=</span><span class="n">access_token</span><span class="p">)</span>

<span class="c"># now use client to make calendar operations...</span>
</pre></div>
</div>
<div class="section" id="conclusion">
<h3>Conclusion</h3>
<p>The main reason I wrote this blog post is I wanted to show a concrete example of
using RSA-SHA1 and version 2.0 of the Google API together. All of the
information I have presented is in the Google documentation, but it is spread
across several documents and jumbled up with example code for version 1.0 and
HMAC-SHA1.  Do not be afraid to look at the source code for the Python client
library.  Despite Google's strange habit of ignoring <a class="reference external" href="http://www.python.org/dev/peps/pep-0008/">PEP-8</a> and using
LongJavaLikeMethodNames, the code is logical and easy to read. Their library is
built up in layers, and you may have to dip down a few levels to find out what
is going on, but it is fairly straightforward to read if you combine it with
their online documentation.</p>
<p>I hope someone finds this useful. Your feedback is welcome.</p>
</div>
</div>
]]></content>
  </entry>
  <entry>
    <author>
      <name>Brian Neal</name>
      <uri>http://deathofagremmie.com</uri>
    </author>
    <title type="html"><![CDATA[Contributing to open source - a success story and advice for newbies]]></title>
    <link rel="alternate" type="text/html" href="http://deathofagremmie.com/2011/06/23/contributing-to-open-source-a-success-story-and-advice-for-newbies" />
    <id>http://deathofagremmie.com/2011/06/23/contributing-to-open-source-a-success-story-and-advice-for-newbies</id>
    <updated>2011-06-23T21:45:00Z</updated>
    <published>2011-06-23T21:45:00Z</published>
    <category scheme="http://deathofagremmie.com" term="Subversion" />
    <category scheme="http://deathofagremmie.com" term="Open Source" />
    <summary type="html"><![CDATA[Contributing to open source - a success story and advice for newbies]]></summary>
    <content type="html" xml:base="http://deathofagremmie.com/2011/06/23/contributing-to-open-source-a-success-story-and-advice-for-newbies"><![CDATA[<div class="document">
<p>Recently, my team at work found a <a class="reference external" href="http://subversion.tigris.org/issues/show_bug.cgi?id=3919">bug in Subversion</a>, I submitted a patch, and it
was accepted!  This was very exciting for me so I thought I would share this
story in the hopes of inspiring others to contribute to open source projects.
It may not be as hard as you might think!</p>
<div class="section" id="the-bug">
<h3>The Bug</h3>
<p>We use <a class="reference external" href="http://subversion.apache.org/">Subversion</a> at work for revision control. My colleague and I were trying
to merge a branch back to trunk when we ran into some strange behavior. We make
use of Subversion properties, which allow you to attach arbitrary metadata to
files and directories. Our project has to deliver our source code and
documentation to the customer in a required directory format (can you guess who
our customer is?). However not all files need to be sent to the customer. To
solve this problem we use a simple &quot;yes/no&quot; delivery property on each file to
control whether it is delivered or not. Before making a delivery, a script is
run that prunes out the files that have the delivery flag set to &quot;no&quot;.</p>
<p>When our merge was completed, many files were marked with having merge conflicts
on the delivery property. Looking through the logs it was discovered that after
we had made our branch, someone had changed the delivery property on some files
to &quot;yes&quot; on the trunk. Someone else had also changed the delivery property
independently to &quot;yes&quot; on the branch. When we attempted to merge the branch back
to trunk, we were getting merge conflicts, even though we were trying to change
the delivery property value to &quot;yes&quot; on both the trunk and branch. Why was this
a conflict? This didn't seem quite right.</p>
<p>I signed up for the Subversion user's mailing list and made a short post
summarizing our issue. I later learned that it is proper etiquette to attach a
bash script that can demonstrate the problem. Despite this, a Subversion developer
took interest in my post and created a test script in an attempt to reproduce our
issue. At first it looked like he could not reproduce the problem. However another
helpful user pointed out a bug in his script. Once this was fixed, the developer
declared our problem a genuine bug and created a ticket for it in the issue tracker.</p>
</div>
<div class="section" id="the-patch">
<h3>The Patch</h3>
<p>Impressed by all of this, I thanked him for his efforts and tentatively asked if
I could help. The developer told me which file and function he thought the
problem might be in. I downloaded the Subversion source and began looking at the
code. I was fairly impressed with the code quality, so I decided I would try to
create a patch for the bug over the weekend. We really wanted that bug fixed,
and I was genuinely curious to see if I would be able to figure something out.
It would be an interesting challenge and a test of my novice open source skills.</p>
<p>When the weekend came I began a more thorough examination of the Subversion
website. The Subversion team has done a great job in providing documentation on
their development process. This includes a contributing guide and patch
submittal process. I also discovered they had recently added a makefile that
downloaded the Subversion source code and the source for all of Subversion's
dependencies. The makefile then builds everything with debug turned on. Wow! It
took me a few tries to get this working, but the problems were because I did not
have all the development tools installed on my Ubuntu box.  Once this was
sorted, everything went smoothly, and in a matter of minutes I had a Subversion
executable I could run under the gdb debugger. Nice!</p>
<p>I studied the code for about an hour, peeking and poking at a few things in the
debugger. I used the script the developer wrote to recreate the problem. I
wasn't quite sure what I was doing, as I was brand new to this code base. But
the code was clearly written and commented well. My goal was to get a patch that
was in the 80-100% complete range. I wanted to do enough work that a core
developer would be able to see what I was doing and either commit it outright or
easily fill in the parts that I missed. After a while I thought I had a solution
and generated a patch. I sent it to the Subversion developer's mailing list as
per the contributing guide.</p>
</div>
<div class="section" id="the-wait">
<h3>The Wait</h3>
<p>Next I began probably the worst part for a contributor. I had to wait and see if
I got any feedback. On some open source projects a patch may languish for months.
It all depends on the number of developers and how busy they are. My chances
didn't look good as the developers were in the initial stages of getting a
beta version of 1.7 out the door. It was also not clear to me who &quot;owned&quot; the
issue tracker. On some projects, the issue tracker is wide open to the
community. Was I supposed to update the ticket? I wasn't quite sure, and the
contributing guide was silent on this issue. I eventually concluded I was not;
it looked like only committers were using the tracker. Patches were being
discussed on the mailing list instead of in the tracker. This is a bit different
than some projects I am familiar with.</p>
<p>I didn't have to wait long. After a few days, the original developer who
confirmed my bug took interest again. He looked at my patch, and thought I had
missed something. He suggested a change and asked for my opinion. I looked at
the code again; it seemed like a good change and I told him I agreed. I also
warned him I was brand new to the code, and to take my opinion with a grain a
salt. After running my change against the tests, he then committed my patch!
One small victory for open source!</p>
</div>
<div class="section" id="final-thoughts">
<h3>Final Thoughts</h3>
<p>So what went right here? I have to hand it to the Subversion team. They have
been in business a long time, and they have excellent documentation for people
who want to contribute. The makefile they created that sets up a complete
development environment most definitely tipped the scale for me and enabled me
to create my patch. Without that I'm not sure I would have had the time or
patience to get all that unfamiliar source code built. The Subversion team has
really worked hard at lowering the barrier for new contributors.</p>
<p>My advice to people who want to contribute to open source but aren't quite sure
how to go about doing it:</p>
<ul class="simple">
<li>Spend some time reading the documentation. This includes any FAQ's and
contributor guides (if any).</li>
<li>Monitor the user and developer mailing lists to get a feel for how the
community operates. Each project has different customs and traditions.</li>
<li>You may also wish to hang out on the project's IRC channel for the same
reason.</li>
<li>When writing on the mailing lists, be extremely concise and polite.
You don't want to waste anyone's time, and you don't want to
be seen as someone who thinks they are entitled to a fix. Just remember you
are the new guy. You can't just barge in and make demands.</li>
<li>Ask how you can help. Nothing makes a developer happier when someone asks how
they can help. Remember, most of the people in the community are volunteers.</li>
<li>Open source can sometimes be &quot;noisy&quot;. There will be people who
won't quite understand your issue and may hurriedly suggest an incorrect solution or give
incomplete advice. Study their responses and be polite. You may also wish to resist the temptation
to reply right away. This is especially hard when you are new and you don't
know who the &quot;real&quot; developers are. However you should assume everyone is trying to
help.</li>
<li>Finally, be patient. Again, most folks are volunteers. They have real jobs,
families and lives. The project may also be preoccupied with other tasks, like
getting a beta out the door. Now may not be a good time for a brand new
feature, or your bug may not be considered a show stopper to the majority of
the community.</li>
</ul>
<p>A big thank-you to Stefan Sperling from the Subversion team who shepherded my
bug report and patch through their process.</p>
<p>I hope this story encourages you to contribute to open source software!</p>
</div>
</div>
]]></content>
  </entry>
  <entry>
    <author>
      <name>Brian Neal</name>
      <uri>http://deathofagremmie.com</uri>
    </author>
    <title type="html"><![CDATA[My newline-to-break extension now shipping with Python-Markdown]]></title>
    <link rel="alternate" type="text/html" href="http://deathofagremmie.com/2011/06/21/my-newline-to-break-extension-now-shipping-with-python-markdown" />
    <id>http://deathofagremmie.com/2011/06/21/my-newline-to-break-extension-now-shipping-with-python-markdown</id>
    <updated>2011-06-21T22:15:00Z</updated>
    <published>2011-06-21T22:15:00Z</published>
    <category scheme="http://deathofagremmie.com" term="Python" />
    <category scheme="http://deathofagremmie.com" term="Markdown" />
    <summary type="html"><![CDATA[My newline-to-break extension now shipping with Python-Markdown]]></summary>
    <content type="html" xml:base="http://deathofagremmie.com/2011/06/21/my-newline-to-break-extension-now-shipping-with-python-markdown"><![CDATA[<div class="document">
<p>Here is a quick update on a <a class="reference external" href="http://deathofagremmie.com/2011/05/09/a-newline-to-break-python-markdown-extension/">previous post</a> I made about a newline-to-break
extension for <a class="reference external" href="http://www.freewisdom.org/projects/python-markdown/">Python-Markdown</a>. I'm very happy to report that the extension will
now be <a class="reference external" href="https://github.com/waylan/Python-Markdown/issues/13">shipping with Python-Markdown</a>! Thanks to developer Waylan Limberg for
including it!</p>
</div>
]]></content>
  </entry>
  <entry>
    <author>
      <name>Brian Neal</name>
      <uri>http://deathofagremmie.com</uri>
    </author>
    <title type="html"><![CDATA[Django Uploads and UnicodeEncodeError]]></title>
    <link rel="alternate" type="text/html" href="http://deathofagremmie.com/2011/06/04/django-uploads-and-unicodeencodeerror" />
    <id>http://deathofagremmie.com/2011/06/04/django-uploads-and-unicodeencodeerror</id>
    <updated>2011-06-04T20:00:00Z</updated>
    <published>2011-06-04T20:00:00Z</published>
    <category scheme="http://deathofagremmie.com" term="Python" />
    <category scheme="http://deathofagremmie.com" term="Linux" />
    <category scheme="http://deathofagremmie.com" term="Unicode" />
    <category scheme="http://deathofagremmie.com" term="Django" />
    <summary type="html"><![CDATA[Django Uploads and UnicodeEncodeError]]></summary>
    <content type="html" xml:base="http://deathofagremmie.com/2011/06/04/django-uploads-and-unicodeencodeerror"><![CDATA[<div class="document">
<p>Something strange happened that I wish to document in case it helps others.  I
had to reboot my Ubuntu server while troubleshooting a disk problem. After the
reboot, I began receiving internal server errors whenever someone tried to view
a certain forum thread on my <a class="reference external" href="http://djangoproject.com">Django</a> powered website. After some detective work,
I determined it was because a user that had posted in the thread had an avatar
image whose filename contained non-ASCII characters. The image file had been
there for months, and I still cannot explain why it just suddenly started
happening.</p>
<p>The traceback I was getting ended with something like this:</p>
<div class="highlight"><pre><span class="n">File</span> <span class="s">&quot;/django/core/files/storage.py&quot;</span><span class="p">,</span> <span class="n">line</span> <span class="mi">159</span><span class="p">,</span> <span class="ow">in</span> <span class="n">_open</span>
<span class="k">return</span> <span class="n">File</span><span class="p">(</span><span class="nb">open</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">path</span><span class="p">(</span><span class="n">name</span><span class="p">),</span> <span class="n">mode</span><span class="p">))</span>

<span class="ne">UnicodeEncodeError</span><span class="p">:</span> <span class="s">&#39;ascii&#39;</span> <span class="n">codec</span> <span class="n">can</span><span class="s">&#39;t encode characters in position 72-79: ordinal not in range(128)</span>
</pre></div>
<p>So it appeared that the <tt class="docutils literal">open()</tt> call was triggering the error. This led me on
a twisty Google search which had many dead ends. Eventually I found a suitable
explanation. Apparently, Linux filesystems don't enforce a particular Unicode
encoding for filenames. Linux applications must decide how to interpret
filenames all on their own. The Python OS library (on Linux) uses environment
variables to determine what locale you are in, and this chooses the encoding for
filenames.  If these environment variables are not set, Python falls back to
ASCII (by default), and hence the source of my <tt class="docutils literal">UnicodeEncodeError</tt>.</p>
<p>So how do you tell a Python instance that is running under Apache / <tt class="docutils literal">mod_wsgi</tt>
about these environment variables? It turns out the answer is in the <a class="reference external" href="https://docs.djangoproject.com/en/1.3/howto/deployment/modpython/#if-you-get-a-unicodeencodeerror">Django
documentation</a>, albeit in the <tt class="docutils literal">mod_python</tt> integration section.</p>
<p>So, to fix the issue, I added the following lines to my <tt class="docutils literal">/etc/apache2/envvars</tt>
file:</p>
<div class="highlight"><pre><span class="nb">export </span><span class="nv">LANG</span><span class="o">=</span><span class="s1">&#39;en_US.UTF-8&#39;</span>
<span class="nb">export </span><span class="nv">LC_ALL</span><span class="o">=</span><span class="s1">&#39;en_US.UTF-8&#39;</span>
</pre></div>
<p>Note that you must cold stop and re-start Apache for these changes to take
effect. I got tripped up at first because I did an <tt class="docutils literal">apache2ctrl
graceful</tt>, and that was not sufficient to create a new environment.</p>
</div>
]]></content>
  </entry>
  <entry>
    <author>
      <name>Brian Neal</name>
      <uri>http://deathofagremmie.com</uri>
    </author>
    <title type="html"><![CDATA[I contributed to Fructose]]></title>
    <link rel="alternate" type="text/html" href="http://deathofagremmie.com/2011/05/31/i-contributed-to-fructose" />
    <id>http://deathofagremmie.com/2011/05/31/i-contributed-to-fructose</id>
    <updated>2011-05-31T21:40:00Z</updated>
    <published>2011-05-31T21:40:00Z</published>
    <category scheme="http://deathofagremmie.com" term="Python" />
    <category scheme="http://deathofagremmie.com" term="Unit Testing" />
    <category scheme="http://deathofagremmie.com" term="C++" />
    <category scheme="http://deathofagremmie.com" term="Fructose" />
    <summary type="html"><![CDATA[I contributed to Fructose]]></summary>
    <content type="html" xml:base="http://deathofagremmie.com/2011/05/31/i-contributed-to-fructose"><![CDATA[<div class="document">
<p>At work we started using <a class="reference external" href="http://cxxtest.tigris.org/">CxxTest</a> as our unit testing framework. We like it because
it is very light-weight and easy to use. We've gotten a tremendous amount of benefit
from using a unit testing framework, much more than I had ever imagined. We now have
almost 700 tests, and I cannot imagine going back to the days of no unit tests or ad-hoc
testing. It is incredibly reassuring to see all the tests pass after making a significant
change to the code base. There is no doubt in my mind that our software-hardware integration
phases have gone much smoother thanks to our unit tests.</p>
<p>Sadly it seems CxxTest is no longer actively supported. However this is not of great
concern to us. The code is so small we are fairly confident we could tweak it if necessary.</p>
<p>I recently discovered <a class="reference external" href="http://fructose.sourceforge.net/">Fructose</a>, a unit testing framework written by Andrew Marlow. It too
has similar goals of being small and simple to use. One thing I noticed that CxxTest had that
Fructose did not was a Python code generator that took care of creating the <tt class="docutils literal">main()</tt> function
and registering all the tests with the framework. Since C++ has very little introspection
capabilities, C++ unit testing frameworks have historically laid the burden of registering
tests on the programmer. Some use macros to help with this chore, but littering your code
with ugly macros makes tests annoying to write. And if anything, you want your tests to be
easy to write so your colleagues will write lots of tests. CxxTest approached this problem by
providing first a Perl script, then later a Python script, to automate this part of the process.</p>
<p>I decided it would be interesting to see if I could provide such a script for Fructose. After
a Saturday of hacking, I'm happy to say Andrew has accepted the script and it now ships with
Fructose version 1.1.0. I hope to improve the script to not only run all the tests but to also
print out a summary of the number of tests that passed and failed at the end, much like CxxTest does.
This will require some changes to the C++ code. Also on my wish list is to make the script
extensible, so that others can easily change the output and code generation to suit their needs.</p>
<p>I've hosted the code for the Python script, which I call <tt class="docutils literal">fructose_gen.py</tt> on <a class="reference external" href="https://bitbucket.org/bgneal/fructose_gen/src">Bitbucket</a>.
Feedback is greatly appreciated.</p>
</div>
]]></content>
  </entry>
  <entry>
    <author>
      <name>Brian Neal</name>
      <uri>http://deathofagremmie.com</uri>
    </author>
    <title type="html"><![CDATA[A newline-to-break Python-Markdown extension]]></title>
    <link rel="alternate" type="text/html" href="http://deathofagremmie.com/2011/05/09/a-newline-to-break-python-markdown-extension" />
    <id>http://deathofagremmie.com/2011/05/09/a-newline-to-break-python-markdown-extension</id>
    <updated>2011-05-09T22:40:00Z</updated>
    <published>2011-05-09T22:40:00Z</published>
    <category scheme="http://deathofagremmie.com" term="Python" />
    <category scheme="http://deathofagremmie.com" term="Markdown" />
    <summary type="html"><![CDATA[A newline-to-break Python-Markdown extension]]></summary>
    <content type="html" xml:base="http://deathofagremmie.com/2011/05/09/a-newline-to-break-python-markdown-extension"><![CDATA[<div class="document">
<p>When I launched a new version of my website, I decided the new forums would use
<a class="reference external" href="http://daringfireball.net/projects/markdown/">Markdown</a> instead of <a class="reference external" href="http://en.wikipedia.org/wiki/BBCode">BBCode</a> for the markup. This decision was mainly a personal
one for aesthetic reasons. I felt that Markdown was more natural to write compared
to the clunky square brackets of BBCode.</p>
<p>My new site is coded in <a class="reference external" href="http://python.org">Python</a> using the <a class="reference external" href="http://djangoproject.com">Django</a> framework. For a Markdown implementation
I chose <a class="reference external" href="http://www.freewisdom.org/projects/python-markdown/">Python-Markdown</a>.</p>
<p>My mainly non-technical users seemed largely ambivalent to the change from
BBCode to Markdown. This was probably because I gave them a nice Javascript editor
(<a class="reference external" href="http://markitup.jaysalvat.com/home/">MarkItUp!</a>) which inserted the correct markup for them.</p>
<p>However, shortly after launch, one particular feature of Markdown really riled up
some users: the default line break behavior. In strict Markdown, to create a new
paragraph, you must insert a blank line between paragraphs. Hard returns (newlines)
are simply ignored, just like they are in HTML. You can, however, force a break by
ending a line with two blank spaces. This isn't very intuitive, unlike the rest of
Markdown.</p>
<p>Now I agree the default behavior is useful if you are creating an online document, like a blog post.
However, non-technical users really didn't understand this behavior at all in the context
of a forum post. For example, many of my users post radio-show playlists, formatted with
one song per line. When such a playlist was pasted into a forum post, Markdown made it
all one giant run-together paragraph. This did not please my users. Arguably, they should
have used a Markdown list. But it became clear teaching people the new syntax wasn't
going to work, especially when it used to work just fine in BBCode and they had created
their playlists in the same way for several years.</p>
<p>It turns out I am not alone in my observations (or on the receiving end of user wrath). Other,
much larger sites, like <a class="reference external" href="http://blog.stackoverflow.com/2009/10/markdown-one-year-later/">StackOverflow</a> and <a class="reference external" href="http://github.github.com/github-flavored-markdown/">GitHub</a>, have altered their Markdown parsers
to treat newlines as hard breaks. How can this be done with Python-Markdown?</p>
<p>It turns out this is really easy. Python-Markdown was designed with user customization
in mind by offering an extension facility. The <a class="reference external" href="http://www.freewisdom.org/projects/python-markdown/Writing_Extensions">extension documentation</a> is good,
and you can find extension writing help on the friendly <a class="reference external" href="http://lists.sourceforge.net/lists/listinfo/python-markdown-discuss">mailing list</a>.</p>
<p>Here is a simple extension for Python-Markdown that turns newlines into HTML &lt;br /&gt; tags.</p>
<div class="highlight"><pre><span class="sd">&quot;&quot;&quot;</span>
<span class="sd">A python-markdown extension to treat newlines as hard breaks; like</span>
<span class="sd">StackOverflow and GitHub flavored Markdown do.</span>

<span class="sd">&quot;&quot;&quot;</span>
<span class="kn">import</span> <span class="nn">markdown</span>


<span class="n">BR_RE</span> <span class="o">=</span> <span class="s">r&#39;\n&#39;</span>

<span class="k">class</span> <span class="nc">Nl2BrExtension</span><span class="p">(</span><span class="n">markdown</span><span class="o">.</span><span class="n">Extension</span><span class="p">):</span>

    <span class="k">def</span> <span class="nf">extendMarkdown</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">md</span><span class="p">,</span> <span class="n">md_globals</span><span class="p">):</span>
        <span class="n">br_tag</span> <span class="o">=</span> <span class="n">markdown</span><span class="o">.</span><span class="n">inlinepatterns</span><span class="o">.</span><span class="n">SubstituteTagPattern</span><span class="p">(</span><span class="n">BR_RE</span><span class="p">,</span> <span class="s">&#39;br&#39;</span><span class="p">)</span>
        <span class="n">md</span><span class="o">.</span><span class="n">inlinePatterns</span><span class="o">.</span><span class="n">add</span><span class="p">(</span><span class="s">&#39;nl&#39;</span><span class="p">,</span> <span class="n">br_tag</span><span class="p">,</span> <span class="s">&#39;_end&#39;</span><span class="p">)</span>


<span class="k">def</span> <span class="nf">makeExtension</span><span class="p">(</span><span class="n">configs</span><span class="o">=</span><span class="bp">None</span><span class="p">):</span>
    <span class="k">return</span> <span class="n">Nl2BrExtension</span><span class="p">(</span><span class="n">configs</span><span class="p">)</span>
</pre></div>
<p>I saved this code in a file called <tt class="docutils literal">mdx_nl2br.py</tt> and put it on my <tt class="docutils literal">PYTHONPATH</tt>. You can then use
it in a Django template like this:</p>
<div class="highlight"><pre><span class="cp">{{</span> <span class="nv">value</span><span class="o">|</span><span class="nf">markdown</span><span class="s2">:&quot;nl2br&quot;</span> <span class="cp">}}</span><span class="x"></span>
</pre></div>
<p>To use the extension in Python code, something like this should do the trick:</p>
<div class="highlight"><pre><span class="kn">import</span> <span class="nn">markdown</span>
<span class="n">md</span> <span class="o">=</span> <span class="n">markdown</span><span class="o">.</span><span class="n">Markdown</span><span class="p">(</span><span class="n">safe_mode</span><span class="o">=</span><span class="bp">True</span><span class="p">,</span> <span class="n">extensions</span><span class="o">=</span><span class="p">[</span><span class="s">&#39;nl2br&#39;</span><span class="p">])</span>
<span class="n">converted_text</span> <span class="o">=</span> <span class="n">md</span><span class="o">.</span><span class="n">convert</span><span class="p">(</span><span class="n">text</span><span class="p">)</span>
</pre></div>
<p><strong>Update (June 21, 2011):</strong> This extension is now being distributed with
Python-Markdown! See <a class="reference external" href="https://github.com/waylan/Python-Markdown/issues/13">issue 13 on github</a> for the details. Thanks to Waylan
Limberg for the help in creating the extension and for including it with
Python-Markdown.</p>
</div>
]]></content>
  </entry>
  <entry>
    <author>
      <name>Brian Neal</name>
      <uri>http://deathofagremmie.com</uri>
    </author>
    <title type="html"><![CDATA[A better "Who's Online" with Redis & Python]]></title>
    <link rel="alternate" type="text/html" href="http://deathofagremmie.com/2011/04/25/a-better-who-s-online-with-redis-python" />
    <id>http://deathofagremmie.com/2011/04/25/a-better-who-s-online-with-redis-python</id>
    <updated>2011-04-25T12:00:00Z</updated>
    <published>2011-04-25T12:00:00Z</published>
    <category scheme="http://deathofagremmie.com" term="Python" />
    <category scheme="http://deathofagremmie.com" term="Redis" />
    <summary type="html"><![CDATA[A better "Who's Online" with Redis & Python]]></summary>
    <content type="html" xml:base="http://deathofagremmie.com/2011/04/25/a-better-who-s-online-with-redis-python"><![CDATA[<div class="document">
<p><strong>Updated on December 17, 2011:</strong> I found a better solution. Head on over to
the <a class="reference external" href="http://deathofagremmie.com/2011/12/17/who-s-online-with-redis-python-a-slight-return/">new post</a> to check it out.</p>
<div class="section" id="who-s-what">
<h3>Who's What?</h3>
<p>My website, like many others, has a &quot;who's online&quot; feature. It displays the
names of authenticated users that have been seen over the course of the last ten
minutes or so. It may seem a minor feature at first, but I find it really does a lot to
&quot;humanize&quot; the site and make it seem more like a community gathering place.</p>
<p>My first implementation of this feature used the MySQL database to update a
per-user timestamp whenever a request from an authenticated user arrived.
Actually, this seemed excessive to me, so I used a strategy involving an &quot;online&quot;
cookie that has a five minute expiration time. Whenever I see an authenticated
user without the online cookie I update their timestamp and then hand them back
a cookie that will expire in five minutes. In this way I don't have to hit the
database on every single request.</p>
<p>This approach worked fine but it has some aspects that didn't sit right with me:</p>
<ul class="simple">
<li>It seems like overkill to use the database to store temporary, trivial information like
this. It doesn't feel like a good use of a full-featured relational database
management system (RDBMS).</li>
<li>I am writing to the database during a GET request. Ideally, all GET requests should
be idempotent. Of course if this is strictly followed, it would be
impossible to create a &quot;who's online&quot; feature in the first place. You'd have
to require the user to POST data periodically. However, writing to a RDBMS
during a GET request is something I feel guilty about and try to avoid when I
can.</li>
</ul>
</div>
<div class="section" id="redis">
<h3>Redis</h3>
<p>Enter <a class="reference external" href="http://redis.io/">Redis</a>. I discovered Redis recently, and it is pure, white-hot
awesomeness. What is Redis? It's one of those projects that gets slapped with
the &quot;NoSQL&quot; label. And while I'm still trying to figure that buzzword out, Redis makes
sense to me when described as a lightweight data structure server.
<a class="reference external" href="http://memcached.org/">Memcached</a> can store key-value pairs very fast, where the value is always a string.
Redis goes one step further and stores not only strings, but data
structures like lists, sets, and hashes. For a great overview of what Redis is
and what you can do with it, check out <a class="reference external" href="http://simonwillison.net/static/2010/redis-tutorial/">Simon Willison's Redis tutorial</a>.</p>
<p>Another reason why I like Redis is that it is easy to install and deploy.
It is straight C code without any dependencies. Thus you can build it from
source just about anywhere. Your Linux distro may have a package for it, but it
is just as easy to grab the latest tarball and build it yourself.</p>
<p>I've really come to appreciate Redis for being such a small and lightweight
tool. At the same time, it is very powerful and effective for filling those
tasks that a traditional RDBMS is not good at.</p>
<p>For working with Redis in Python, you'll need to grab Andy McCurdy's <a class="reference external" href="https://github.com/andymccurdy/redis-py">redis-py</a>
client library. It can be installed with a simple</p>
<div class="highlight"><pre><span class="nv">$ </span>sudo pip install redis
</pre></div>
</div>
<div class="section" id="who-s-online-with-redis">
<h3>Who's Online with Redis</h3>
<p>Now that we are going to use Redis, how do we implement a &quot;who's online&quot;
feature? The first step is to get familiar with the <a class="reference external" href="http://redis.io/commands">Redis API</a>.</p>
<p>One approach to the &quot;who's online&quot; problem is to add a user name to a set
whenever we see a request from that user. That's fine but how do we know when
they have stopped browsing the site? We have to periodically clean out the
set in order to time people out. A cron job, for example, could delete the
set every five minutes.</p>
<p>A small problem with deleting the set is that people will abruptly disappear
from the site every five minutes. In order to give more gradual behavior we
could utilize two sets, a &quot;current&quot; set and an &quot;old&quot; set. As users are seen, we
add their names to the current set. Every five minutes or so (season to taste),
we simply overwrite the old set with the contents of the current set, then clear
out the current set. At any given time, the set of who's online is the union
of these two sets.</p>
<p>This approach doesn't give exact results of course, but it is perfectly fine for my site.</p>
<p>Looking over the Redis API, we see that we'll be making use of the following
commands:</p>
<ul class="simple">
<li><a class="reference external" href="http://redis.io/commands/sadd">SADD</a> for adding members to the current set.</li>
<li><a class="reference external" href="http://redis.io/commands/rename">RENAME</a> for copying the current set to the old, as well as destroying the
current set all in one step.</li>
<li><a class="reference external" href="http://redis.io/commands/sunion">SUNION</a> for performing a union on the current and old sets to produce the set
of who's online.</li>
</ul>
<p>And that's it! With these three primitives we have everything we need. This is
because of the following useful Redis behaviors:</p>
<ul class="simple">
<li>Performing a <tt class="docutils literal">SADD</tt> against a set that doesn't exist creates the set and is
not an error.</li>
<li>Performing a <tt class="docutils literal">SUNION</tt> with sets that don't exist is fine; they are simply
treated as empty sets.</li>
</ul>
<p>The one caveat involves the <tt class="docutils literal">RENAME</tt> command. If the key you wish to rename
does not exist, the Python Redis client treats this as an error and an exception
is thrown.</p>
<p>Experimenting with algorithms and ideas is quite easy with Redis. You can either
use the Python Redis client in a Python interactive interpreter shell, or you can
use the command-line client that comes with Redis. Either way you can quickly
try out commands and refine your approach.</p>
</div>
<div class="section" id="implementation">
<h3>Implementation</h3>
<p>My website is powered by <a class="reference external" href="http://djangoproject.com">Django</a>, but I am not going to show any Django specific
code here. Instead I'll show just the pure Python parts, and hopefully you can
adapt it to whatever framework, if any, you are using.</p>
<p>I created a Python module to hold this functionality:
<tt class="docutils literal">whos_online.py</tt>. Throughout this module I use a lot of exception handling,
mainly because if the Redis server has crashed (or if I forgot to start it, say
in development) I don't want my website to be unusable. If Redis is unavailable,
I simply log an error and drive on. Note that in my limited experience Redis is
very stable and has not crashed on me once, but it is good to be defensive.</p>
<p>The first important function used throughout this module is a function to obtain
a connection to the Redis server:</p>
<div class="highlight"><pre><span class="kn">import</span> <span class="nn">logging</span>
<span class="kn">import</span> <span class="nn">redis</span>

<span class="n">logger</span> <span class="o">=</span> <span class="n">logging</span><span class="o">.</span><span class="n">getLogger</span><span class="p">(</span><span class="n">__name__</span><span class="p">)</span>

<span class="k">def</span> <span class="nf">_get_connection</span><span class="p">():</span>
    <span class="sd">&quot;&quot;&quot;</span>
<span class="sd">    Create and return a Redis connection. Returns None on failure.</span>
<span class="sd">    &quot;&quot;&quot;</span>
    <span class="k">try</span><span class="p">:</span>
        <span class="n">conn</span> <span class="o">=</span> <span class="n">redis</span><span class="o">.</span><span class="n">Redis</span><span class="p">(</span><span class="n">host</span><span class="o">=</span><span class="n">HOST</span><span class="p">,</span> <span class="n">port</span><span class="o">=</span><span class="n">PORT</span><span class="p">,</span> <span class="n">db</span><span class="o">=</span><span class="n">DB</span><span class="p">)</span>
        <span class="k">return</span> <span class="n">conn</span>
    <span class="k">except</span> <span class="n">redis</span><span class="o">.</span><span class="n">RedisError</span><span class="p">,</span> <span class="n">e</span><span class="p">:</span>
        <span class="n">logger</span><span class="o">.</span><span class="n">error</span><span class="p">(</span><span class="n">e</span><span class="p">)</span>

    <span class="k">return</span> <span class="bp">None</span>
</pre></div>
<p>The <tt class="docutils literal">HOST</tt>, <tt class="docutils literal">PORT</tt>, and <tt class="docutils literal">DB</tt> constants can come from a
configuration file or they could be module-level constants. In my case they are set in my
Django <tt class="docutils literal">settings.py</tt> file. Once we have this connection object, we are free to
use the Redis API exposed via the Python Redis client.</p>
<p>To update the current set whenever we see a user, I call this function:</p>
<div class="highlight"><pre><span class="c"># Redis key names:</span>
<span class="n">USER_CURRENT_KEY</span> <span class="o">=</span> <span class="s">&quot;wo_user_current&quot;</span>
<span class="n">USER_OLD_KEY</span> <span class="o">=</span> <span class="s">&quot;wo_user_old&quot;</span>

<span class="k">def</span> <span class="nf">report_user</span><span class="p">(</span><span class="n">username</span><span class="p">):</span>
 <span class="sd">&quot;&quot;&quot;</span>
<span class="sd"> Call this function when a user has been seen. The username will be added to</span>
<span class="sd"> the current set.</span>
<span class="sd"> &quot;&quot;&quot;</span>
 <span class="n">conn</span> <span class="o">=</span> <span class="n">_get_connection</span><span class="p">()</span>
 <span class="k">if</span> <span class="n">conn</span><span class="p">:</span>
     <span class="k">try</span><span class="p">:</span>
         <span class="n">conn</span><span class="o">.</span><span class="n">sadd</span><span class="p">(</span><span class="n">USER_CURRENT_KEY</span><span class="p">,</span> <span class="n">username</span><span class="p">)</span>
     <span class="k">except</span> <span class="n">redis</span><span class="o">.</span><span class="n">RedisError</span><span class="p">,</span> <span class="n">e</span><span class="p">:</span>
         <span class="n">logger</span><span class="o">.</span><span class="n">error</span><span class="p">(</span><span class="n">e</span><span class="p">)</span>
</pre></div>
<p>If you are using Django, a good spot to call this function is from a piece
of <a class="reference external" href="http://docs.djangoproject.com/en/1.3/topics/http/middleware/">custom middleware</a>. I kept my &quot;5 minute cookie&quot; algorithm to avoid doing this on
every request although it is probably unnecessary on my low traffic site.</p>
<p>Periodically you need to &quot;age out&quot; the sets by destroying the old set, moving
the current set to the old set, and then emptying the current set.</p>
<div class="highlight"><pre><span class="k">def</span> <span class="nf">tick</span><span class="p">():</span>
    <span class="sd">&quot;&quot;&quot;</span>
<span class="sd">    Call this function to &quot;age out&quot; the old set by renaming the current set</span>
<span class="sd">    to the old.</span>
<span class="sd">    &quot;&quot;&quot;</span>
    <span class="n">conn</span> <span class="o">=</span> <span class="n">_get_connection</span><span class="p">()</span>
    <span class="k">if</span> <span class="n">conn</span><span class="p">:</span>
       <span class="c"># An exception may be raised if the current key doesn&#39;t exist; if that</span>
       <span class="c"># happens we have to delete the old set because no one is online.</span>
       <span class="k">try</span><span class="p">:</span>
           <span class="n">conn</span><span class="o">.</span><span class="n">rename</span><span class="p">(</span><span class="n">USER_CURRENT_KEY</span><span class="p">,</span> <span class="n">USER_OLD_KEY</span><span class="p">)</span>
       <span class="k">except</span> <span class="n">redis</span><span class="o">.</span><span class="n">ResponseError</span><span class="p">:</span>
           <span class="k">try</span><span class="p">:</span>
               <span class="k">del</span> <span class="n">conn</span><span class="p">[</span><span class="n">old</span><span class="p">]</span>
           <span class="k">except</span> <span class="n">redis</span><span class="o">.</span><span class="n">RedisError</span><span class="p">,</span> <span class="n">e</span><span class="p">:</span>
               <span class="n">logger</span><span class="o">.</span><span class="n">error</span><span class="p">(</span><span class="n">e</span><span class="p">)</span>
       <span class="k">except</span> <span class="n">redis</span><span class="o">.</span><span class="n">RedisError</span><span class="p">,</span> <span class="n">e</span><span class="p">:</span>
           <span class="n">logger</span><span class="o">.</span><span class="n">error</span><span class="p">(</span><span class="n">e</span><span class="p">)</span>
</pre></div>
<p>As mentioned previously, if no one is on your site, eventually your current set
will cease to exist as it is renamed and not populated further. If you attempt to
rename a non-existent key, the Python Redis client raises a <tt class="docutils literal">ResponseError</tt> exception.
If this occurs we just manually delete the old set. In a bit of Pythonic cleverness,
the Python Redis client supports the <tt class="docutils literal">del</tt> syntax to support this operation.</p>
<p>The <tt class="docutils literal">tick()</tt> function can be called periodically by a cron job, for example. If you are using Django,
you could create a <a class="reference external" href="http://docs.djangoproject.com/en/1.3/howto/custom-management-commands/">custom management command</a> that calls <tt class="docutils literal">tick()</tt> and schedule cron
to execute it. Alternatively, you could use something like <a class="reference external" href="http://celeryproject.org/">Celery</a> to schedule a
job to do the same. (As an aside, Redis can be used as a back-end for Celery, something that I hope
to explore in the near future).</p>
<p>Finally, you need a way to obtain the current &quot;who's online&quot; set, which again is
a union of the current and old sets.</p>
<div class="highlight"><pre><span class="k">def</span> <span class="nf">get_users_online</span><span class="p">():</span>
    <span class="sd">&quot;&quot;&quot;</span>
<span class="sd">    Returns a set of user names which is the union of the current and old</span>
<span class="sd">    sets.</span>
<span class="sd">    &quot;&quot;&quot;</span>
    <span class="n">conn</span> <span class="o">=</span> <span class="n">_get_connection</span><span class="p">()</span>
    <span class="k">if</span> <span class="n">conn</span><span class="p">:</span>
        <span class="k">try</span><span class="p">:</span>
            <span class="c"># Note that keys that do not exist are considered empty sets</span>
            <span class="k">return</span> <span class="n">conn</span><span class="o">.</span><span class="n">sunion</span><span class="p">([</span><span class="n">USER_CURRENT_KEY</span><span class="p">,</span> <span class="n">USER_OLD_KEY</span><span class="p">])</span>
        <span class="k">except</span> <span class="n">redis</span><span class="o">.</span><span class="n">RedisError</span><span class="p">,</span> <span class="n">e</span><span class="p">:</span>
            <span class="n">logger</span><span class="o">.</span><span class="n">error</span><span class="p">(</span><span class="n">e</span><span class="p">)</span>

    <span class="k">return</span> <span class="nb">set</span><span class="p">()</span>
</pre></div>
<p>In my Django application, I calling this function from a <a class="reference external" href="http://docs.djangoproject.com/en/1.3/howto/custom-template-tags/#inclusion-tags">custom inclusion template tag</a>
.</p>
</div>
<div class="section" id="conclusion">
<h3>Conclusion</h3>
<p>I hope this blog post gives you some idea of the usefulness of Redis. I expanded
on this example to also keep track of non-authenticated &quot;guest&quot; users. I simply added
another pair of sets to track IP addresses.</p>
<p>If you are like me, you are probably already thinking about shifting some functions that you
awkwardly jammed onto a traditional database to Redis and other &quot;NoSQL&quot;
technologies.</p>
</div>
</div>
]]></content>
  </entry>
</feed>

