<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom"><title>Tomatohater</title><link href="https://tomatohater.com/" rel="alternate"></link><link href="https://tomatohater.com/feeds/all.atom.xml" rel="self"></link><id>https://tomatohater.com/</id><updated>2021-03-12T12:00:00-08:00</updated><entry><title>Long time, no post</title><link href="https://tomatohater.com/2021/03/12/long-time-no-post/" rel="alternate"></link><published>2021-03-12T12:00:00-08:00</published><updated>2021-03-12T12:00:00-08:00</updated><author><name>Drew Engelson</name></author><id>tag:tomatohater.com,2021-03-12:/2021/03/12/long-time-no-post/</id><summary type="html">&lt;p&gt;So much has changed since my last post (almost 4 years ago!)&lt;/p&gt;</summary><content type="html">&lt;p&gt;I haven't written anything here since May 2017, but since then there
have been so many changes in my life. I moved my family to the Seattle
area and started a new job at &lt;a href="https://www.starbucks.com/"&gt;Starbucks&lt;/a&gt;.
I'm waaaaay overdue for my "just started a new gig" post, so I'd to
catch you up over a series of posts in the coming weeks.&lt;/p&gt;
&lt;p&gt;While I've always been a tree hugger at heart, most recently I've been
on a journey (a.k.a. obsession) to drive digital sustainability at
Starbucks; that means reducing the carbon footprint caused my our
digital business.&lt;/p&gt;
&lt;p&gt;It's an ongoing process, but my next step is to use
&lt;a href="https://tomatohater.com/"&gt;Tomatohater.com&lt;/a&gt; as a proving ground to
minimize the carbon emissions of this website, and to discuss the
results and methodologies. I'll need to begin by baselining this
site's current carbon footprint. I'll look at hosting and data
transfer, I already have a good start here since I use a static site
generator (Pelican) and am heavily cached at a CDN that invests in
renewable energy (Cloudflare). The primary opportunity will be in the
arena of minimizing the page size and any assets need at runtime. I'll
also consider the energy used by web browsers use render this site.&lt;/p&gt;
&lt;p&gt;I love what &lt;a href="https://solar.lowtechmagazine.com/"&gt;Low Tech Magazine's solar powered
website&lt;/a&gt; has done and consider it a
prime example of how far one can go down this path.&lt;/p&gt;
&lt;p&gt;More to follow!&lt;/p&gt;</content><category term="Technology"></category><category term="starbucks"></category></entry><entry><title>I'm speaking at PGCon 2017 (The PostgreSQL Conference)</title><link href="https://tomatohater.com/2017/05/23/im-speaking-at-pgcon-2017-the-postgresql-conference/" rel="alternate"></link><published>2017-05-23T11:00:00-07:00</published><updated>2017-05-23T11:00:00-07:00</updated><author><name>Drew Engelson</name></author><id>tag:tomatohater.com,2017-05-23:/2017/05/23/im-speaking-at-pgcon-2017-the-postgresql-conference/</id><summary type="html">&lt;p&gt;Cracking passwords in Postgres with Python and Django password
hashers&lt;/p&gt;</summary><content type="html">&lt;p&gt;Later this week, I'll be heading to Ottawa, Ontario to attend &lt;a href="https://www.pgcon.org/2017/"&gt;PGCon
2017&lt;/a&gt; where I'll be delivering a talk
entitled &lt;em&gt;Pgcrypto Avasat! A study in Django's password hashers&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;If that doesn't get you excited... I don't know what will!&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;If you've ever found yourself with a list of hashed passwords and
    wondered how to crack them, this is the talk for you.&lt;/li&gt;
&lt;li&gt;If you've ever been curious about the PGCRYPTO extension in
    Postgres, this is the talk for you.&lt;/li&gt;
&lt;li&gt;If you enjoy doing somewhat nefarious things with Python, this is
    the talk for you.&lt;/li&gt;
&lt;li&gt;If you've ever thought about building custom authentication in
    Django, this is the talk for you.&lt;/li&gt;
&lt;li&gt;If you've didn't even know that Django has a thing called PASSWORD
    HASHERS, this is very likely the talk for you.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I'll post the slides and video once all is said and done.&lt;/p&gt;
&lt;p&gt;If you'll be at PGCon this year, come find me. I'd love to talk!&lt;/p&gt;
&lt;p&gt;&lt;img alt="pgcon-2017-logo" src="https://www.pgcon.org/2017/images/pgcon-728x90.png"&gt;&lt;/p&gt;</content><category term="Technology"></category><category term="python"></category><category term="django"></category><category term="postgres"></category><category term="postgresql"></category><category term="conferences"></category><category term="talks"></category><category term="celerity"></category></entry><entry><title>[Meetup] Building Smarter Offices</title><link href="https://tomatohater.com/2016/07/19/meetup-building-smarter-offices/" rel="alternate"></link><published>2016-07-19T15:41:00-07:00</published><updated>2016-07-19T15:41:00-07:00</updated><author><name>Drew Engelson</name></author><id>tag:tomatohater.com,2016-07-19:/2016/07/19/meetup-building-smarter-offices/</id><summary type="html">&lt;p&gt;Join our discussion on how to "Safely Steal a Conference Room".&lt;/p&gt;</summary><content type="html">&lt;p&gt;My company, &lt;a href="http://www.hugeinc.com/"&gt;Huge&lt;/a&gt;, will begin hosting a meetup
series entitled &lt;em&gt;Building Smarter Offices&lt;/em&gt; tomorrow. We'll
be discussing some of the smart office work that we've been doing since
moving into our new &lt;a href="http://dcinno.streetwise.co/2016/05/09/office-envy-dc-tech-tour-huges-office-in-union-market-ne-dc/"&gt;Union Market
offices&lt;/a&gt;.
We'll share the work, and we're eager to learn how your office
experience might be improved through user experience and technology.&lt;/p&gt;
&lt;p&gt;The first talk is entitled &lt;a href="http://www.meetup.com/Huge-DC/events/232287394/"&gt;Safely Steal a Conference
Room&lt;/a&gt; and will be led
by our technical architect, &lt;a href="https://twitter.com/toigo"&gt;Matt Toigo&lt;/a&gt;.
Come join us at 6:30pm on June 20th for food/drinks and to learn more.
More info on &lt;a href="http://www.hugeinc.com/events/building-smarter-offices-workshop"&gt;Huge
Events&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img alt="helloroom-photo" src="http://photos3.meetupstatic.com/photos/event/3/5/f/600_452280863.jpeg"&gt;&lt;/p&gt;
&lt;p&gt;Thanks again, &lt;a href="https://twitter.com/eschlaik"&gt;Erica&lt;/a&gt;, for making this happen.&lt;/p&gt;</content><category term="Technology"></category><category term="huge"></category><category term="smart-office"></category></entry><entry><title>Huge DC Hosts React DC June Meetup</title><link href="https://tomatohater.com/2016/06/28/huge-dc-hosts-react-dc-june-meetup/" rel="alternate"></link><published>2016-06-28T10:41:00-07:00</published><updated>2016-06-28T10:41:00-07:00</updated><author><name>Drew Engelson</name></author><id>tag:tomatohater.com,2016-06-28:/2016/06/28/huge-dc-hosts-react-dc-june-meetup/</id><summary type="html">&lt;p&gt;Join React DC at Huge's new DC digs.&lt;/p&gt;</summary><content type="html">&lt;p&gt;Come join us tomorrow night at our snazzy new offices in DC's Union
Market as we host the &lt;a href="http://www.meetup.com/React-DC/events/232113774/"&gt;React DC June
Meetup&lt;/a&gt;. We've posted
some details on the &lt;a href="http://www.hugeinc.com/events/react-dc-meetup"&gt;Huge
Events&lt;/a&gt; website too.&lt;/p&gt;
&lt;p&gt;The main speaker will be &lt;a href="http://johnkpaul.com/"&gt;John K. Paul&lt;/a&gt; on
&lt;em&gt;ES2015: The NOS for your React Development Engine&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Big thanks to &lt;a href="https://twitter.com/toigo"&gt;Matt&lt;/a&gt;,
&lt;a href="https://twitter.com/eschlaik"&gt;Erica&lt;/a&gt;, and the &lt;a href="http://www.meetup.com/React-DC/members/?op=leaders"&gt;React DC
organizers&lt;/a&gt; for
making this happen.&lt;/p&gt;
&lt;p&gt;&lt;img alt="react-dc-logo" src="https://pbs.twimg.com/profile_images/565522537157644289/a2L3k9yL.png"&gt;&lt;/p&gt;</content><category term="Technology"></category><category term="react"></category><category term="huge"></category><category term="javascript"></category></entry><entry><title>Anti-semitic NYTimes Crossword?</title><link href="https://tomatohater.com/2014/12/07/anti-semitic-nytimes-crossword/" rel="alternate"></link><published>2014-12-07T11:41:00-08:00</published><updated>2014-12-07T11:41:00-08:00</updated><author><name>Drew Engelson</name></author><id>tag:tomatohater.com,2014-12-07:/2014/12/07/anti-semitic-nytimes-crossword/</id><summary type="html">&lt;p&gt;Probably not, but is this puzzle insensitive?&lt;/p&gt;</summary><content type="html">&lt;p&gt;This post is a departure from my typical rantings on technology, but I
feel a need to put this out there.&lt;/p&gt;
&lt;p&gt;Let me start by saying that I absolutely love the &lt;a href="http://wordplay.blogs.nytimes.com/"&gt;New York Times
Crossword Puzzle&lt;/a&gt; and &lt;a href="http://willshortz.com/"&gt;Will
Shortz&lt;/a&gt; is kind of a geek hero to me. I attempt
the puzzle every day via their &lt;a href="https://itunes.apple.com/us/app/the-new-york-times-crossword/id307569751"&gt;official iPhone
app&lt;/a&gt;.
The app also includes "The Mini", a quick puzzle which is just a 5x5
grid and is really fun when you only have a minute or two to spare.&lt;/p&gt;
&lt;h2&gt;The shape of the puzzle&lt;/h2&gt;
&lt;p&gt;Last week, on December 1st, I opened up The Mini which was authored by
crossword prodigy &lt;a href="http://wordplay.blogs.nytimes.com/2009/10/21/wild-guess/"&gt;Joel
Fagliano&lt;/a&gt; and
immediately found myself thinking, "Hmm, this puzzle is shaped like a
swastika!" Now, I'm no conspiracy theorist. I don't believe that I'm
being wronged at every turn. To the contrary, I generally see the humor
and ridiculousness in daily life. So I concluded, "Well, it kinda looks
like a pinwheel too. Likely just a coincidence." And then I moved on to
the clues...&lt;/p&gt;
&lt;h2&gt;One Down&lt;/h2&gt;
&lt;p&gt;The very first clue I attempted happened to be 1-Down:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Captain Kirk or Harrison Ford per Sandler's "The Chanukah Song"&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This clue should have delighted me to no end with it's Star Trek AND
Star Wars connections. Who doesn't love Sandler? And being a proud
"JEW" myself, the answer is one that I could personally identify with.&lt;/p&gt;
&lt;p&gt;&lt;img alt="crossword-1down" src="/theme/img/posts/antisemetic-crossword-1down.png"&gt;&lt;/p&gt;
&lt;p&gt;What a minute! Did that really just happen? Given the context of a
puzzle arguably shaped like a swastika and the answer "JEW" in a prime
spot... my blood started to curdle. So I wrote to the editor via the
in-app feedback link.&lt;/p&gt;
&lt;h2&gt;NY Times responds&lt;/h2&gt;
&lt;p&gt;The following day I received an email response that, while probably
accurate, was less than sympathetic.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Thank you for writing to The New York Times.&lt;/p&gt;
&lt;p&gt;I can assure you that Mr. Fagliano meant no harm in the pattern of
squares for today's Mini. As I'm sure you probably know, there are
only so many possible arrangements for squares in a 5x5 puzzle. I had
to look really hard at it in order to see what you refer to as a
swastika, and personally, I mainly see a lot of white space.&lt;/p&gt;
&lt;p&gt;Also, the entry JEW had no connection to the pattern of squares in the
puzzle. The clue itself is a line from Adam Sandler's "The Chanukkah
Song."&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;My opinion&lt;/h2&gt;
&lt;p&gt;I really do believe that Joel Fagliano had no malintent with this
puzzle. Yet I am still left feeling unsatisfied. The NY Times response
basically accused me of dreaming up the swastika shape. To prove that it
wasn't just me, I presented the puzzle to a bunch of other individuals.
Each answered within seconds and provided only two distinct answers:
"swastika" and "Nazi symbol". No one mentioned "pinwheel" and no
one even said, "I don't know."&lt;/p&gt;
&lt;p&gt;I suppose I fault the NY Times Crossword editorial staff for not
catching this. And even once it got out, there has been no
acknowledgement that the puzzle &lt;em&gt;could&lt;/em&gt; have been interpreted as
insensitive.&lt;/p&gt;
&lt;h2&gt;What next?&lt;/h2&gt;
&lt;p&gt;Leave me some comments with your thoughts on this. If you are
disappointed by this puzzle, please let the editors know by sending a
tweet to &lt;a href="https://twitter.com/NYTimesWordplay"&gt;@NYTimesWordplay&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;What am I going to do? Keep on keepin' on. I'll still do my
crosswords. I'll still scratch my head over the Sunday puzzle. I only
hope that this post yields something constructive. Perhaps driving the
crossword staff to consider their audience more carefully; I know they
can do better. And maybe we'll eventually see an improved NY Times
Crossword product for everyone.&lt;/p&gt;</content><category term="Politics"></category><category term="crossword"></category><category term="anti-semitism"></category><category term="will-shortz"></category><category term="judiasm"></category></entry><entry><title>I'm speaking at ModevCon 2014</title><link href="https://tomatohater.com/2014/12/06/im-speaking-at-modevcon-2014/" rel="alternate"></link><published>2014-12-06T11:05:00-08:00</published><updated>2014-12-06T11:05:00-08:00</updated><author><name>Drew Engelson</name></author><id>tag:tomatohater.com,2014-12-06:/2014/12/06/im-speaking-at-modevcon-2014/</id><summary type="html">&lt;p&gt;Join us at the east coast's premier mobile development event&lt;/p&gt;</summary><content type="html">&lt;p&gt;Jay Kissman and I will be presenting &lt;strong&gt;The Native App vs. Mobile Web
Debate&lt;/strong&gt; at &lt;a href="http://modevcon.com/"&gt;ModevCon 2014&lt;/a&gt;, "the east coast's
premier mobile development event." The conference, formerly called
ModevEast, takes place on December 11-12 at Artisphere in Arlington, VA.
Here is our talk's description from the ModevCon website:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Choosing whether to build a mobile website or native application (or
both!) is a decision that every organization needs to make for itself
when defining it's mobile and content strategies. During this talk,
Celerity's Jay Kissman (native nut) and Drew Engelson (mobile web
nerd) will debate the pros and cons of each and review some of the
critical decisions organizations face when making the choice.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Oh, and don't miss our very own iOS/3D guru, David Donze, and his talk,
&lt;strong&gt;Accelerating Apps with OpenGL&lt;/strong&gt;. &lt;a href="http://www.celerity.com/"&gt;Celerity&lt;/a&gt;
is also a gold sponsor of the event.&lt;/p&gt;
&lt;p&gt;Follow the event &lt;a href="https://twitter.com/@gomodev"&gt;@gomodev&lt;/a&gt; and hashtag
&lt;a href="https://twitter.com/hashtag/modevcon"&gt;#modevcon&lt;/a&gt;. Hope to see you
there!&lt;/p&gt;</content><category term="Technology"></category><category term="modevcon"></category><category term="presentations"></category><category term="celerity"></category><category term="drew-engelson"></category><category term="mobile"></category></entry><entry><title>Generating a PowerPoint from text files</title><link href="https://tomatohater.com/2014/04/03/generating-a-powerpoint-from-text-files/" rel="alternate"></link><published>2014-04-03T23:09:00-07:00</published><updated>2014-04-03T23:09:00-07:00</updated><author><name>Drew Engelson</name></author><id>tag:tomatohater.com,2014-04-03:/2014/04/03/generating-a-powerpoint-from-text-files/</id><summary type="html">&lt;p&gt;Annoy your colleagues with Python&lt;/p&gt;</summary><content type="html">&lt;p&gt;One of my nerdy colleagues,
&lt;a href="https://twitter.com/mattcaldwell"&gt;@mattcaldwell&lt;/a&gt;, just gave a neat
presentation at a &lt;a href="http://www.meetup.com/django-district/"&gt;Django-District
Meetup&lt;/a&gt; on using &lt;a href="http://www.meetup.com/django-district/events/171785632/"&gt;vim+tmux as
Django IDE&lt;/a&gt;. To
keep it real, he created his presentation using
&lt;a href="https://github.com/tybenz/vimdeck"&gt;vimdeck&lt;/a&gt; and actually presented his
slides inside of vim.&lt;/p&gt;
&lt;p&gt;I figured it would annoy him to no end to see his presentation in
PowerPoint format. So here's a script to do it...&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="ch"&gt;#!/usr/bin/python&lt;/span&gt;
&lt;span class="c1"&gt;# -*- coding: utf-8 -*-&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;os&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;sys&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;cStringIO&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;StringIO&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;argparse&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ArgumentParser&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;pptx&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Presentation&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;pptx.enum.shapes&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;MSO_SHAPE&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;pptx.dml.color&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;RGBColor&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;pptx.util&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Inches&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Pt&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;is_md&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;src&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;Returns true if file exists and has a Markdown extension&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isfile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;src&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;endswith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;.md&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_pages&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;src&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;Returns a list of Markdown filenames in the source directory.&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;src&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;listdir&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;src&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;is_md&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;src&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;build_deck&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pages&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;Returns the actual PowerPoint deck.&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
    &lt;span class="n"&gt;pres&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Presentation&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;blank_slidelayout&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pres&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;slide_layouts&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;pages&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;slide&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pres&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;slides&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add_slide&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;blank_slidelayout&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;left&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;top&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Inches&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;width&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Inches&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;height&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Inches&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;5.63&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;shape&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;slide&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;shapes&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add_shape&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;MSO_SHAPE&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RECTANGLE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;left&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;top&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;width&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                                       &lt;span class="n"&gt;height&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;shape&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fill&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;solid&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;shape&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fill&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fore_color&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rgb&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;RGBColor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;textbox&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;slide&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;shapes&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add_textbox&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;left&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;top&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;width&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;height&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;textframe&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;textbox&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text_frame&lt;/span&gt;

        &lt;span class="n"&gt;para&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;textframe&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;paragraphs&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="n"&gt;run&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;para&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add_run&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;font&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Consolas&amp;quot;&lt;/span&gt;
        &lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;font&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Pt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;font&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;color&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rgb&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;RGBColor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;pres&lt;/span&gt;


&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vm"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;__main__&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;parser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ArgumentParser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;My dorky colleague created a presentation using vimdeck.&lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="s1"&gt;                     The very last thing he would want to see is it as a real&lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="s1"&gt;                     PowerPoint deck. So here you go...&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;epilog&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;This thing returns binary data, so you probably want to pipe&lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="s1"&gt;                the output to a file with a .pptx extension. Oh, and be sure&lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="s1"&gt;                to &amp;quot;pip install python-pptx&amp;quot; first.&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add_argument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;source&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;nargs&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;help&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Path to source&lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="s1"&gt;        directory containing vimdeck markdown files.&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;args&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parse_args&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="n"&gt;source&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;realpath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="n"&gt;deck&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;build_deck&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;get_pages&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

    &lt;span class="n"&gt;output&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;StringIO&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;deck&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;save&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stdout&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getvalue&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Or &lt;a href="https://gist.github.com/tomatohater/9947556"&gt;grab the Gist&lt;/a&gt;. The
only Python dependency is pptx. &lt;code&gt;pip install python-pptx&lt;/code&gt;
will do just fine. For me, this was really just a fun exercise to gain a
better understanding of the pptx library. (note: this example was tested
against python-pptx==0.5.7)&lt;/p&gt;</content><category term="Technology"></category><category term="python"></category><category term="vim"></category><category term="powerpoint"></category><category term="python-pptx"></category></entry><entry><title>Python performance: string formatting with .format vs %</title><link href="https://tomatohater.com/2013/08/30/python-performance-string-formatting/" rel="alternate"></link><published>2013-08-30T10:12:00-07:00</published><updated>2013-08-30T10:12:00-07:00</updated><author><name>Drew Engelson</name></author><id>tag:tomatohater.com,2013-08-30:/2013/08/30/python-performance-string-formatting/</id><summary type="html">&lt;p&gt;Which is faster?&lt;/p&gt;</summary><content type="html">&lt;p&gt;Python 2.6 introduced the
&lt;a href="http://docs.python.org/2/library/string.html#format-string-syntax"&gt;str.format()&lt;/a&gt;
method for formatting strings which provides a much more flexible
alternative to the older &lt;a href="http://docs.python.org/2/library/stdtypes.html#string-formatting-operations"&gt;modulo
(%)&lt;/a&gt;
based string formatting. But which one performs better? Let's test it
out by repeating a simple string format a million times with each method
and timing the execution.&lt;sup id="fnref:1"&gt;&lt;a class="footnote-ref" href="#fn:1"&gt;1&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;h2&gt;str.format vs. (%) modulo&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;timeit&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;timeit&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_modulo&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;Don&lt;/span&gt;&lt;span class="se"&gt;\&amp;#39;&lt;/span&gt;&lt;span class="s1"&gt;t &lt;/span&gt;&lt;span class="si"&gt;%s&lt;/span&gt;&lt;span class="s1"&gt;, I&lt;/span&gt;&lt;span class="se"&gt;\&amp;#39;&lt;/span&gt;&lt;span class="s1"&gt;m the &lt;/span&gt;&lt;span class="si"&gt;%s&lt;/span&gt;&lt;span class="s1"&gt;.&amp;#39;&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;worry&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;Doctor&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_format&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;Don&lt;/span&gt;&lt;span class="se"&gt;\&amp;#39;&lt;/span&gt;&lt;span class="s1"&gt;t &lt;/span&gt;&lt;span class="si"&gt;{0}&lt;/span&gt;&lt;span class="s1"&gt;, I&lt;/span&gt;&lt;span class="se"&gt;\&amp;#39;&lt;/span&gt;&lt;span class="s1"&gt;m the &lt;/span&gt;&lt;span class="si"&gt;{1}&lt;/span&gt;&lt;span class="s1"&gt;.&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;worry&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;Doctor&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;timeit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stmt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;test_modulo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;number&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1000000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# 0.31221508979797363&lt;/span&gt;

&lt;span class="n"&gt;timeit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stmt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;test_format&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;number&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1000000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# 0.5489029884338379&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Hmmm, the modulo operator is almost twice as fast as str.format() in
these simple examples.&lt;/p&gt;
&lt;p&gt;In cases where you don't require the flexibility of str.format() and
simply want sheer perfomance... Modulo has got to be the way to go...
But according to &lt;a href="http://www.python.org/dev/peps/pep-3101/"&gt;PEP-3101&lt;/a&gt;,
str.format() is intended to replace the modulo operator... Let's hope
that doesn't happen anytime soon.&lt;/p&gt;
&lt;h2&gt;Template strings&lt;/h2&gt;
&lt;p&gt;For completeness, Python also supports string templates (introduced in
Python 2.4). Let's put that to the test.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;string&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Template&lt;/span&gt;

&lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Template&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Don&lt;/span&gt;&lt;span class="se"&gt;\&amp;#39;&lt;/span&gt;&lt;span class="s1"&gt;t $what, I&lt;/span&gt;&lt;span class="se"&gt;\&amp;#39;&lt;/span&gt;&lt;span class="s1"&gt;m the $who.&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_template&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;substitute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;what&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;worry&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;who&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Doctor&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;timeit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stmt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;test_template&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;number&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1000000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# 6.010946035385132&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Ouch!&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Update: 5/12/2017:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Python 3.6 introduced
&lt;a href="https://docs.python.org/3/whatsnew/3.6.html#whatsnew36-pep498"&gt;f-strings&lt;/a&gt;
which provide a new method of string formatting. Let's see how
f-strings perform...&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;timeit&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;timeit&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_fstring&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;worry&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;Doctor&amp;#39;&lt;/span&gt;
    &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Don&lt;/span&gt;&lt;span class="se"&gt;\&amp;#39;&lt;/span&gt;&lt;span class="s1"&gt;t &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s1"&gt;, I&lt;/span&gt;&lt;span class="se"&gt;\&amp;#39;&lt;/span&gt;&lt;span class="s1"&gt;m the &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s1"&gt;.&amp;#39;&lt;/span&gt;

&lt;span class="n"&gt;timeit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stmt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;test_fstring&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;number&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1000000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# 0.276932206004858&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Well, there we have it... f-strings combine the elegance of .format(),
yet is slightly faster than the modulo operator. Big thanks to
&lt;a href="https://disqus.com/by/stopspazzing/"&gt;@StopSpazzing&lt;/a&gt; for pointing this
out.&lt;/p&gt;
&lt;div class="footnote"&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id="fn:1"&gt;
&lt;p&gt;Tests were performed on a MacBook Pro (2.4 Ghz Intel Core i7) and
Python 2.7.2. With Spotify playing the Melvins in the background.&amp;#160;&lt;a class="footnote-backref" href="#fnref:1" title="Jump back to footnote 1 in the text"&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</content><category term="Technology"></category><category term="python"></category><category term="performance"></category><category term="string-formatting"></category></entry><entry><title>Want Heroku Apache (PHP), but getting Node.js stack?</title><link href="https://tomatohater.com/2013/08/29/want-heroku-apache-php-but-getting-nodejs-stack/" rel="alternate"></link><published>2013-08-29T15:42:00-07:00</published><updated>2013-08-29T15:42:00-07:00</updated><author><name>Drew Engelson</name></author><id>tag:tomatohater.com,2013-08-29:/2013/08/29/want-heroku-apache-php-but-getting-nodejs-stack/</id><summary type="html">&lt;p&gt;Forcing the Heroku PHP buildpack&lt;/p&gt;</summary><content type="html">&lt;p&gt;I've been using Heroku to host a bunch of &lt;a href="http://kennethreitz.org/static-sites-on-heroku-cedar/"&gt;static
sites&lt;/a&gt; using the
Apache/PHP hack. All I need is an index.php file in the root of my
application to hint to Heroku that I want the PHP (Apache) buildpack...
and then I disable PHP altogether since I really just want Apache to
serve a static site. But some of my recent attempts at creating these
stacks have results in Heroku giving me a Node.js stack instead...
WTF?!?&lt;/p&gt;
&lt;p&gt;Turns out that some of the newer node-based tools for managing static
assets require a "package.json" file, which makes Heroku think you
want a Node.js stack.&lt;/p&gt;
&lt;h2&gt;Quick solution&lt;/h2&gt;
&lt;p&gt;Specify the buildpack when creating the app.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ heroku create myapp --buildpack https://github.com/heroku/heroku-buildpack-php
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Or specify the buildpack as a config parameter.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ heroku create myapp
$ heroku config:set &lt;span class="nv"&gt;BUILDPACK_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;https://github.com/heroku/heroku-buildpack-php
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This will force Heroku to use the buildpack you want.&lt;/p&gt;
&lt;p&gt;With the rapid evolution of front end tools for managing your Javascript
and CSS assets, we're seeing Node-based tools crop up all over the
place... In fact, Node Package Manager (npm) is quickly becoming the de
facto standard for installing most new web tools. This is a great thing,
however, we're really beginning to blur the lines between front and
back end technologies. Since Heroku attempts to introspect an app to
determine which stack to create, the existence of package.json creates
confusion. And because the PHP stack even isn't &lt;a href="https://devcenter.heroku.com/articles/buildpacks"&gt;officially
supported&lt;/a&gt;, the Node
stack wins. We just need to be more explicit these days.&lt;/p&gt;</content><category term="Technology"></category><category term="heroku"></category><category term="apache"></category><category term="static"></category><category term="php"></category><category term="node.js"></category></entry><entry><title>Tomatohater.com now a Pelican/GitHub site</title><link href="https://tomatohater.com/2013/06/08/tomatohatercom-now-a-pelicangithub-site/" rel="alternate"></link><published>2013-06-08T00:36:00-07:00</published><updated>2013-06-08T00:36:00-07:00</updated><author><name>Drew Engelson</name></author><id>tag:tomatohater.com,2013-06-08:/2013/06/08/tomatohatercom-now-a-pelicangithub-site/</id><summary type="html">&lt;p&gt;Statically generated for cms-less publishing&lt;/p&gt;</summary><content type="html">&lt;p&gt;This site is now completely static, generated by
&lt;a href="http://docs.getpelican.com/"&gt;Pelican&lt;/a&gt; and hosted on &lt;a href="http://pages.github.com/"&gt;GitHub
Pages&lt;/a&gt;. Ridiculously simple and elgant.&lt;/p&gt;
&lt;h2&gt;The evolution of tomatohater.com:&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;Dates                    Technology                     Hosting
-----------------------  -----------------------------  ---------------
Original (2008-2010)     django-basic-blog              Amazon EC2
May 2010 - Dec 2011      django-mingus                  Amazon EC2
Dec 2011 - Mar 2012      Wordpress (custom)             Heroku
Mar 2012 - Jun 2013      Wordpress (custom)             Bluehost
Jun 2013 -               Static, generated by Pelican   GitHub Pages
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;You can check out the source code at
&lt;a href="https://github.com/tomatohater/tomatohater.github.io/tree/source"&gt;https://github.com/tomatohater/tomatohater.github.io/tree/source&lt;/a&gt;&lt;/p&gt;</content><category term="Technology"></category><category term="python"></category><category term="cms"></category><category term="static-sites"></category><category term="pelican"></category></entry><entry><title>Faster! Faster! Accelerate your business with blazing prototypes</title><link href="https://tomatohater.com/2012/07/19/faster-faster-accelerate-your-business-with-blazing-prototypes/" rel="alternate"></link><published>2012-07-19T00:54:00-07:00</published><updated>2012-07-19T00:54:00-07:00</updated><author><name>Drew Engelson</name></author><id>tag:tomatohater.com,2012-07-19:/2012/07/19/faster-faster-accelerate-your-business-with-blazing-prototypes/</id><summary type="html">&lt;p&gt;My slides from OSCON 2012&lt;/p&gt;</summary><content type="html">&lt;p&gt;Slides for my presentation &lt;em&gt;Faster! Faster! Accelerate your business with
blazing prototypes&lt;/em&gt; at OSCON 2012.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.slideshare.net/OReillyOSCON/faster-faster-accelerate-your-business-with-blazing-prototypes"&gt;&lt;img alt="PGCon 2017 logo" src="https://image.slidesharecdn.com/engelson-deck-120718194510-phpapp02/95/slide-1-728.jpg"&gt;&lt;/a&gt;&lt;/p&gt;</content><category term="Technology"></category><category term="flask"></category><category term="innovation"></category><category term="oscon"></category><category term="prototypes"></category><category term="python"></category></entry><entry><title>I'm speaking at OSCON 2012</title><link href="https://tomatohater.com/2012/05/11/im-speaking-at-oscon-2012/" rel="alternate"></link><published>2012-05-11T04:11:00-07:00</published><updated>2012-05-11T04:11:00-07:00</updated><author><name>Drew Engelson</name></author><id>tag:tomatohater.com,2012-05-11:/2012/05/11/im-speaking-at-oscon-2012/</id><summary type="html">&lt;p&gt;Join me at O'Reilly Media's Open Source Convention&lt;/p&gt;</summary><content type="html">&lt;p&gt;I'll be heading to Portland, OR in July since my talk entitled
&lt;strong&gt;"Faster! Faster! Accelerate your business with blazing prototypes"&lt;/strong&gt;
was accepted for &lt;a href="https://www.youtube.com/watch?v=lD_LC8Ra0EE"&gt;OSCON
2012&lt;/a&gt; (Open Source
Convention).&lt;/p&gt;
&lt;p&gt;The general idea is three fold:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Why do businesses still choose COTS solutions? I'll attempt to
    answer that question and provide some weaponry to help get past that
    unfortunate reality.&lt;/li&gt;
&lt;li&gt;Then I'll reveal my survey results of a number of common
    development frameworks and assess their fitness for "rapid
    prototyping". We'll attempt to identify which tools can bring a
    concept in to real working code in the least amount of time and with
    the least amount of pain.&lt;/li&gt;
&lt;li&gt;Finally, we'll walk through a simple prototyping example using
    one of these frameworks.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;|&lt;/p&gt;
&lt;p&gt;If you haven't already, &lt;a href="https://en.oreilly.com/oscon2012/public/register"&gt;get
registered&lt;/a&gt; for the
conference. Use code
&lt;a href="https://en.oreilly.com/oscon2012/public/register"&gt;**OS12FOS**&lt;/a&gt; an
get &lt;strong&gt;20% off registration&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Hope to see you there!&lt;/p&gt;</content><category term="Technology"></category><category term="oscon"></category><category term="presentations"></category></entry><entry><title>Migrating a Django app to Heroku</title><link href="https://tomatohater.com/2012/01/23/migrating-a-django-app-to-heroku/" rel="alternate"></link><published>2012-01-23T01:48:00-08:00</published><updated>2012-01-23T01:48:00-08:00</updated><author><name>Drew Engelson</name></author><id>tag:tomatohater.com,2012-01-23:/2012/01/23/migrating-a-django-app-to-heroku/</id><summary type="html">&lt;p&gt;Heroku supports Python! What are we waiting for?&lt;/p&gt;</summary><content type="html">&lt;p&gt;As a Python and Django kind of guy, I had always been jealous of the
Ruby on Rails folks. This has nothing at all to do with the framework
itself. No, no, no.... Django all the way. It was the
&lt;a href="http://www.heroku.com"&gt;Heroku&lt;/a&gt; cloud application platform that had me
longing.&lt;/p&gt;
&lt;p&gt;Yes, I could run my Django application on Google App Engine, but that
requires all sorts of hackery and my app ended up an abomination of the
original... too unnatural for my tastes.&lt;/p&gt;
&lt;p&gt;I sensed a small shift in the Earth's rotation on Sept 28, 2011. This
is when Heroku added &lt;a href="http://blog.heroku.com/archives/2011/9/28/python_and_django/"&gt;support for
Python/Django&lt;/a&gt;
on their platform. I needed to give it a test drive and I was amazed how
simple it was. The following is an account of what I did to port one of
my existing Django sites to Heroku...&lt;/p&gt;
&lt;h2&gt;Initial setup&lt;/h2&gt;
&lt;p&gt;Heroku already provides a decent &lt;a href="http://devcenter.heroku.com/articles/python"&gt;quick start guide for
Python&lt;/a&gt;. That's a great
place to begin. Check out the &lt;strong&gt;Prerequisites&lt;/strong&gt; and &lt;strong&gt;Local Workstation
Setup&lt;/strong&gt; which will get you up and running quickly. It helps if you're
already familiar with &lt;a href="http://git-scm.com/"&gt;Git&lt;/a&gt;,
&lt;a href="http://pypi.python.org/pypi/virtualenv"&gt;virtualenv&lt;/a&gt; and
&lt;a href="http://pypi.python.org/pypi/pip"&gt;pip&lt;/a&gt;. If you're not, then now is an
excellent time to learn!&lt;/p&gt;
&lt;p&gt;First things first. Assuming your Django project is already in Git,
change directory to the project root. Then...&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ heroku auth:login
... output omitted ...
$ heroku create --stack cedar
... output omitted ...
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Believe it or not, we're almost there!&lt;/p&gt;
&lt;h2&gt;Database config&lt;/h2&gt;
&lt;p&gt;Now, my existing app is running a MySQL database. I'm going to use the
built-in Postgres DB on Heroku. So I need to update my Django
settings...&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# Database config&lt;/span&gt;
&lt;span class="n"&gt;DATABASES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="s1"&gt;&amp;#39;default&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;ENGINE&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;django.db.backends.postgresql_psycopg2&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Note, that I didn't supply any database info or credentials. Heroku
auromatically injects this info into your settings.py file. I also need
to dump out my current database which I can later import into my new app
(this could also be done with fixtures).&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ python myapp/manage.py dumpdata &amp;gt; db.sql
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;I'm already storing my Python dependencies in a
&lt;a href="http://www.pip-installer.org/en/latest/requirements.html"&gt;requirements.txt&lt;/a&gt;
file. If you're not, you'll need to create this file at the project
root.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ pip freeze &amp;gt; requirements.txt
$ cat requirements.txt
&lt;span class="nv"&gt;Django&lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;.3
&lt;span class="nv"&gt;feedparser&lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt;.1
&lt;span class="nv"&gt;gunicorn&lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.12.2
&lt;span class="nv"&gt;lxml&lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;.3.3
&lt;span class="nv"&gt;psycopg2&lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;.4.2
python-dateutil&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;.5
python-sunlightapi&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;.1.0
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Don't forget to commit your changes! Then push to Heroku.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ git commit -a -m &lt;span class="s1"&gt;&amp;#39;Mods to run on Heroku.&amp;#39;&lt;/span&gt;
$ git push heroku master
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;That second line above is really something to be admired. Not only does
it push the code to Heroku's repository, but then it triggers a real
deployment. Heroku automatically copies the files to the stack, installs
all the dependencies (via requirements.txt), detects that this is a
Django application, and runs the application with "runserver". Poof!
Done.&lt;/p&gt;
&lt;p&gt;Well, not quite done. But really that's bulk of the it and my app is in
fact running. You can confirm this by using the "heroku ps" command.&lt;/p&gt;
&lt;h2&gt;Running your app&lt;/h2&gt;
&lt;p&gt;Now I need to do all the normal Django setup stuff, like syncdb and
loaddata...&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ heroku run python myapp/manage.py syncdb
$ heroku run python myapp/manage.py loaddata &amp;lt; db.sql
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;If all goes well, I should be able to hit my site with a web browser at
the wacky hostname provided by Heroku when I created the stack. Herou
provides a shortcut...&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ heroku open
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;Final thoughts&lt;/h2&gt;
&lt;p&gt;We'll that was pretty darn easy. A few other things to note if you're
trying this yourself.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;You'll need to serve static media from somewhere. I used
    &lt;a href="https://docs.djangoproject.com/en/dev/howto/static-files/#using-django-contrib-staticfiles"&gt;django.contrib.staticfiles&lt;/a&gt;
    in this example, but that's probably not idea for production.
    Though the output does get cached... so it's also not too bad.&lt;/li&gt;
&lt;li&gt;You don't want to use the built-in Django runserver. I prefer
    &lt;a href="http://gunicorn.org/"&gt;gunicorn&lt;/a&gt; and it's easy to configure that!&lt;/li&gt;
&lt;li&gt;Enjoy yourself. This is cool stuff!&lt;/li&gt;
&lt;/ul&gt;</content><category term="Technology"></category><category term="cloud"></category><category term="django"></category><category term="heroku"></category><category term="python"></category></entry><entry><title>Dear Congress...</title><link href="https://tomatohater.com/2012/01/19/dear-congress/" rel="alternate"></link><published>2012-01-19T02:20:00-08:00</published><updated>2012-01-19T02:20:00-08:00</updated><author><name>Drew Engelson</name></author><id>tag:tomatohater.com,2012-01-19:/2012/01/19/dear-congress/</id><summary type="html">&lt;p&gt;A note to Congress regarding SOPA and PIPA&lt;/p&gt;</summary><content type="html">&lt;p&gt;Dear Representative Van Hollen, Senator Cardin, and Senator Mikulski:&lt;/p&gt;
&lt;p&gt;The "Stop Online Piracy Act" (H.R. 3261) and the "Preventing Real Online
Threats to Economic Creativity and Theft of Intellectual Property Act of
2011" (S.968) are intended to solve a worthy problem, yet the methods
recommended by these bills I find to be completely offensive.&lt;/p&gt;
&lt;p&gt;I greatly value the protection of intellectual property, yet I place the
values of freedom and free speech even higher. We must not enact a bill
with power to dismantle these very freedoms.&lt;/p&gt;
&lt;p&gt;Please find another way. Punish the offender, not the messenger. I have
confidence that you will find an acceptable alternative.&lt;/p&gt;
&lt;p&gt;Please understand very clearly that your vote for these bills in their
current forms will be countered with a vote against you in your next
election.&lt;/p&gt;
&lt;p&gt;Sincerely,&lt;/p&gt;
&lt;p&gt;Drew Engelson, Cabin John, MD&lt;/p&gt;</content><category term="Technology"></category><category term="congress"></category><category term="opengov"></category><category term="pipa"></category><category term="sopa"></category></entry><entry><title>Custom Django management commands on Heroku</title><link href="https://tomatohater.com/2012/01/17/custom-django-management-commands-on-heroku/" rel="alternate"></link><published>2012-01-17T02:51:00-08:00</published><updated>2012-01-17T02:51:00-08:00</updated><author><name>Drew Engelson</name></author><id>tag:tomatohater.com,2012-01-17:/2012/01/17/custom-django-management-commands-on-heroku/</id><summary type="html">&lt;p&gt;Quick solution to a common Django/Heroku problem.&lt;/p&gt;</summary><content type="html">&lt;p&gt;Running Django management commands is easy on
&lt;a href="http://heroku.com"&gt;Heroku&lt;/a&gt;. For example, to &lt;strong&gt;syncdb&lt;/strong&gt; you simply
execute:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ heroku run python your_app/manage.py syncdb
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Easy enough. But you may find that running a &lt;a href="https://docs.djangoproject.com/en/dev/howto/custom-management-commands/"&gt;custom management
command&lt;/a&gt;
to be a little trickier. You might run into something like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ heroku run python your_app/manage.py your_custom_command
Running python your_app/manage.py your_custom_command attached to terminal... up, run.2
Unknown command: &lt;span class="s1"&gt;&amp;#39;your_custom_command&amp;#39;&lt;/span&gt;
Type &lt;span class="s1"&gt;&amp;#39;manage.py help&amp;#39;&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; usage.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Ouch! And you wouldn't be alone here:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="http://stackoverflow.com/questions/8294363/django-custom-commands-not-showing-up-on-heroku"&gt;Django custom commands not showing up on
    Heroku&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://stackoverflow.com/questions/8380733/running-django-custom-manage-py-task-on-heroku-importing-issues"&gt;Running Django custom manage.py task on Heroku - Importing
    Issues&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://convore.com/django-community/custom-management-command-discovery-fail/"&gt;Custom management command discovery fail
    :(&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;The solution&lt;/h2&gt;
&lt;p&gt;This really just comes down to Python not finding your app. You just
need to adjust your Python path to include your home directory.&lt;/p&gt;
&lt;p&gt;On Heroku, your home directory is generally "/app". You should confirm
this by running:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ heroku run env &lt;span class="p"&gt;|&lt;/span&gt; grep HOME
&lt;span class="nv"&gt;HOME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/app
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;A simple way to adjust your Python path within your Heroku environment
(and not mucking with your app) is by setting the PYTHONPATH env
variable as follows:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ heroku config:add &lt;span class="nv"&gt;PYTHONPATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/app
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;To confirm it is set correctly, run:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ heroku run env &lt;span class="p"&gt;|&lt;/span&gt; grep PYTHONPATH
&lt;span class="nv"&gt;PYTHONPATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/app
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Now you can run your custom management command. This also allows you to
run these as cron (scheduled) tasks:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ heroku run python your_app/manage.py your_custom_command
Success!
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;I hope this post will save you some headbanging. Unless, of course,
it's to the Melvins.&lt;/p&gt;</content><category term="Technology"></category><category term="django"></category><category term="heroku"></category><category term="python"></category></entry><entry><title>Why Django?</title><link href="https://tomatohater.com/2011/11/21/why-django/" rel="alternate"></link><published>2011-11-21T16:06:00-08:00</published><updated>2011-11-21T16:06:00-08:00</updated><author><name>Drew Engelson</name></author><id>tag:tomatohater.com,2011-11-21:/2011/11/21/why-django/</id><summary type="html">&lt;p&gt;In 5 words or less... Why do you use Django?&lt;/p&gt;</summary><content type="html">&lt;p&gt;I have been a heavy Django developer, architect, and evangelist since
about 2006 when &lt;a href="http://nowell.strite.org/"&gt;Nowell Strite&lt;/a&gt; and I first
saw a presentation by &lt;a href="http://conferences.oreillynet.com/cs/os2006/view/e_sess/9153"&gt;Adrian and Jacob at
OSCON&lt;/a&gt;. We
brought Django back to &lt;a href="http://www.pbs.org/"&gt;PBS&lt;/a&gt; where it quickly
became our standard development platform.&lt;/p&gt;
&lt;h2&gt;I know why I use Django...&lt;/h2&gt;
&lt;p&gt;I'm conducting some informal research for a project and I want to hear
from you. &lt;strong&gt;Why you do use Django?&lt;/strong&gt; Please leave a comment here or
tweet me &lt;a href="http://twitter.com/handofdoom"&gt;@handofdoom&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Try to limit your responses to 5 words or less.&lt;/p&gt;</content><category term="Technology"></category><category term="django"></category><category term="python"></category></entry><entry><title>Faetus: An FTP interface to Amazon S3 file storage.</title><link href="https://tomatohater.com/2011/11/10/faetus/" rel="alternate"></link><published>2011-11-10T06:35:00-08:00</published><updated>2011-11-10T06:35:00-08:00</updated><author><name>Drew Engelson</name></author><id>tag:tomatohater.com,2011-11-10:/2011/11/10/faetus/</id><summary type="html">&lt;p&gt;An FTP interface to Amazon S3 file storage.&lt;/p&gt;</summary><content type="html">&lt;h2&gt;What?&lt;/h2&gt;
&lt;p&gt;Faetus is an FTP server that translates FTP commands into Amazon S3 API
calls providing an FTP interface on top of Amazon S3 storage.&lt;/p&gt;
&lt;h2&gt;Why?&lt;/h2&gt;
&lt;p&gt;Amazon's S3 API is awesome and there are plenty of excellent libraries
that make this very simple. However, sometimes you don't have control
over a system, and when that system knows how to talk FTP but not S3,
Faetus is your solution. Read the &lt;a href="http://tomatohater.com/2010/07/15/faetus-v05-released/"&gt;blog
post&lt;/a&gt; for more
info.&lt;/p&gt;
&lt;h2&gt;Download&lt;/h2&gt;
&lt;p&gt;Get the &lt;a href="http://github.com/tomatohater/faetus"&gt;source code&lt;/a&gt; from GitHub.&lt;/p&gt;
&lt;h2&gt;Known issues&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Some FTP clients fail with a socket error when writing a file&lt;/li&gt;
&lt;li&gt;Connections occasionally lost when in passive mode&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Credit where credit is due&lt;/h3&gt;
&lt;p&gt;This project wouldn't have been possible without extensive use of
&lt;a href="http://code.google.com/p/pyftpdlib/"&gt;pyftpdlib&lt;/a&gt; and the work of Chmouel
Boudjnah's &lt;a href="http://github.com/chmouel/ftp-cloudfs"&gt;ftp-cloudfs&lt;/a&gt; from
which Faetus heavily borrows. Thanks!&lt;/p&gt;</content><category term="Technology"></category><category term="amazon-web-services"></category><category term="cloud"></category><category term="faetus"></category><category term="ftp"></category><category term="python"></category><category term="s3"></category></entry><entry><title>pyawschart</title><link href="https://tomatohater.com/2011/11/10/pyawschart/" rel="alternate"></link><published>2011-11-10T06:35:00-08:00</published><updated>2011-11-10T06:35:00-08:00</updated><author><name>Drew Engelson</name></author><id>tag:tomatohater.com,2011-11-10:/2011/11/10/pyawschart/</id><summary type="html">&lt;p&gt;Python library for generating charts for Amazon Web Services based
on Amazon CloudWatch data.&lt;/p&gt;</summary><content type="html">&lt;h2&gt;What?&lt;/h2&gt;
&lt;p&gt;pyawschart is a Python library for generating charts for Amazon Web
Services based on Amazon CloudWatch data.&lt;/p&gt;
&lt;h2&gt;Why?&lt;/h2&gt;
&lt;p&gt;Amazon's cloud services provide infrastructure metrics data via it's
CloudWatch data API. This library aims to render this data more
accessible by creating powerful (and pretty) data visualizations.&lt;/p&gt;
&lt;h2&gt;Download&lt;/h2&gt;
&lt;p&gt;Get the &lt;a href="http://github.com/tomatohater/pyawschart"&gt;source code&lt;/a&gt; from
GitHub.&lt;/p&gt;
&lt;h2&gt;Usage&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;boto&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;pyawschart&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;RANGES&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;pyawschart.rds&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;CPUUtilizationChart&lt;/span&gt;

&lt;span class="c1"&gt;# Connect with boto&lt;/span&gt;
&lt;span class="n"&gt;conn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;boto&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;connect_cloudwatch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;AWS_ACCESS_KEY&lt;/span&gt; &lt;span class="n"&gt;AWS_SECRET_ACCCES_KEY&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;chart&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;CPUUtilizationChart&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;my-rds-instance&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;RANGES&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;hour&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

&lt;span class="c1"&gt;# Display chart url&lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt; &lt;span class="n"&gt;chart&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_url&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="c1"&gt;# Download chart image&lt;/span&gt;
&lt;span class="n"&gt;chart&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;download&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;/tmp/my-rds-instance-cpu-hourly.png&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;Known issues&lt;/h2&gt;
&lt;p&gt;| Please see the GitHub project for known issues and to submit bugs and
  other feedback:&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/tomatohater/pyawschart/issues"&gt;https://github.com/tomatohater/pyawschart/issues&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;Credit where credit is due&lt;/h3&gt;
&lt;p&gt;This project makes extensive use of
&lt;a href="http://boto.cloudhackers.com/"&gt;boto&lt;/a&gt; and &lt;a href="http://pygooglechart.slowchop.com/"&gt;Python Google
Chart&lt;/a&gt;. Thanks!&lt;/p&gt;</content><category term="misc"></category></entry><entry><title>django-simplegravatar v0.2</title><link href="https://tomatohater.com/2011/11/10/django-simplegravatar/" rel="alternate"></link><published>2011-11-10T06:33:00-08:00</published><updated>2011-11-10T06:33:00-08:00</updated><author><name>Drew Engelson</name></author><id>tag:tomatohater.com,2011-11-10:/2011/11/10/django-simplegravatar/</id><summary type="html">&lt;p&gt;Gravatar support for Django projects&lt;/p&gt;</summary><content type="html">&lt;h2&gt;What?&lt;/h2&gt;
&lt;p&gt;A simpler Django template tag to add Gravatar support to your Django
projects. This is based on my 2008 post entitled &lt;a href="http://tomatohater.com/2008/08/16/implementing-gravatar-django/"&gt;Implementing Gravatar
in
Django&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Installation&lt;/h2&gt;
&lt;p&gt;To install this app, simply:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;pip install django-simplegravatar&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;or drop the 'simplegravatar' folder somewhere on your PYTHONPATH&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Add 'simplegravatar' to your projects INSTALLED_APPS list in&lt;/p&gt;
&lt;p&gt;settings.py&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;Configuration&lt;/h2&gt;
&lt;p&gt;There are a few optional settings that can be added to your Django
settings file to affect the global behavior of simplegravatar.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;| **SIMPLEGRAVATAR_SIZE** (default: 80)

Pixel width and height of Gravatar (they are all square)

| **SIMPLEGRAVATAR_RATING** (default: \g\)

g, pg, r, x

| **SIMPLEGRAVATAR_DEFAULT** (default: \\)

| Default image if no Gravatar exists for email. Should be a full image
  URL to your custom image, or one of the Gravatar built-in defaults...

-   404: do not load any image if none is associated with the email
    hash, instead return an HTTP 404 (File Not Found) response
-   mm: (mystery-man) a simple, cartoon-style silhouetted outline of a
    person (does not vary by email hash)
-   identicon: a geometric pattern based on an email hash
-   monsterid: a generated &amp;#39;monster&amp;#39; with different colors, faces, etc
-   wavatar: generated faces with differing features and backgrounds
-   retro: awesome generated, 8-bit arcade-style pixelated faces

| **SIMPLEGRAVATAR_SECURE** (default: False)

Use https?
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;For more information about these default options see:&lt;/p&gt;
&lt;p&gt;&lt;a href="http://en.gravatar.com/site/implement/images/"&gt;http://en.gravatar.com/site/implement/images/&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;USAGE (this goes in your templates)&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;{% load simplegravatar %}
{% show_gravatar &amp;quot;email@address.com&amp;quot; %}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;You may optionally pass a size into this template tag:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;{% show_gravatar &amp;quot;email@address.com&amp;quot; 48 %}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;If you require a secure image (instead of using SETTINGS):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;{% show_gravatar_secure &amp;quot;email@address.com&amp;quot; %}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Enjoy!&lt;/p&gt;</content><category term="misc"></category></entry><entry><title>Concatenating strings in Django templates</title><link href="https://tomatohater.com/2011/09/22/concatenating-strings-in-django-templates/" rel="alternate"></link><published>2011-09-22T15:43:00-07:00</published><updated>2011-09-22T15:43:00-07:00</updated><author><name>Drew Engelson</name></author><id>tag:tomatohater.com,2011-09-22:/2011/09/22/concatenating-strings-in-django-templates/</id><summary type="html">&lt;p&gt;Cheap ass hack to append a string within Django templates.&lt;/p&gt;</summary><content type="html">&lt;p&gt;You might think that joining two strings in a Django template would have
been simple and straightforward. In modern versions of Django, we can
achieve it using the &lt;strong&gt;add&lt;/strong&gt; filter.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;{{&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Mary had a little&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;add&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot; lamb.&amp;quot;&lt;/span&gt; &lt;span class="p"&gt;}}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;However, in earlier versions of Django [TODO: identify specific
version], the add filter only works with numbers. Using it on a string
has no effect.&lt;/p&gt;
&lt;h2&gt;So how to do it?&lt;/h2&gt;
&lt;p&gt;It looks like we're left with the &lt;strong&gt;stringformat&lt;/strong&gt; filter. This feels
quite dirty to me, but it works.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;{{&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Mary had a little&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;stringformat&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;s lamb.&amp;quot;&lt;/span&gt; &lt;span class="p"&gt;}}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Enjoy!&lt;/p&gt;</content><category term="Technology"></category><category term="django"></category><category term="python"></category><category term="templates"></category></entry><entry><title>Recovering from Amazon cloud outage</title><link href="https://tomatohater.com/2011/04/21/recovering-from-amazon-cloud-outage/" rel="alternate"></link><published>2011-04-21T15:41:00-07:00</published><updated>2011-04-21T15:41:00-07:00</updated><author><name>Drew Engelson</name></author><id>tag:tomatohater.com,2011-04-21:/2011/04/21/recovering-from-amazon-cloud-outage/</id><summary type="html">&lt;p&gt;What happens when the AWS cloud has a very, very bad day?&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;a href="http://aws.amazon.com/"&gt;Amazon Web Services&lt;/a&gt; (AWS) have been integral
to the successes of nearly all recent project launches at
&lt;a href="http://www.pbs.org/"&gt;PBS&lt;/a&gt;. All of our core applications are deployed
out on AWS EC2 servers and RDS database instances. While we have
experienced an occasional component failure, these have been infrequent.
When failures have occurred, we have typically been able to leverage the
agility of the cloud to quickly and easily work through them.&lt;/p&gt;
&lt;h2&gt;AWS East is down&lt;/h2&gt;
&lt;p&gt;April 21st was quite another story! Early that morning, AWS began
experiencing connectivity issues affecting Elastic Block Store (EBS)
volumes in the Northern Virginia region (us-east-1), and hence any EC2
or RDS instances that depend on EBS. Oy vey!&lt;/p&gt;
&lt;p&gt;The outage was heavily covered in the press:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="http://technorati.com/technology/it/article/amazon-ec2-outlook-cloudy-with-a/"&gt;Technorati&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.informationweek.com/news/cloud-computing/infrastructure/229402054"&gt;Information
    Week&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.marketwatch.com/video/asset/digits-amazons-cloud-crashes-2011-04-21/C8BA6424-08D4-450A-8855-DF67A2397C8A#!C8BA6424-08D4-450A-8855-DF67A2397C8A"&gt;MarketWatch
    [video]&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.reuters.com/article/2011/04/21/amazon-cloud-idUSN2128319020110421"&gt;Reuters&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;What did this mean for PBS? Well it took out our main portal site
(&lt;a href="http://www.pbs.oprg/"&gt;PBS.org&lt;/a&gt;) and many core services (Merlin API,
COVE video API, TV Schedules API, mobile apps for iPad and iPhone, and
more) for a while. Ouch! While we try to leverage multiple availability
zones where possible, we are entirely in Amazon's East Coast data
centers.&lt;/p&gt;
&lt;h2&gt;Recovering from AWS outage&lt;/h2&gt;
&lt;p&gt;Since the outage affected only EBS-based EC2 and RDS services in the
East region, a path to workaround the outage seems obvious:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Avoid EBS (at least for now)&lt;/li&gt;
&lt;li&gt;Go West, young man!&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This is exactly what we did this morning. We relaunched some
applications from backups on temporarily EBS-less servers. And we
migrated some RDS database servers to the West coast region (us-west-1).
Once this was accomplished, our public facing systems were back online.&lt;/p&gt;
&lt;h2&gt;Looking forward&lt;/h2&gt;
&lt;p&gt;Now what? How can we depend on AWS in the future? Should we migrate our
services elsewhere? There is a universal truth that applies here:
&lt;strong&gt;sh*t happens&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;In practicality, being in the cloud has provided such an improvement in
our overall stability and ability to manage our infrastructure with
minimal resources that I can no longer imagine life without it.&lt;/p&gt;
&lt;p&gt;So the real question is, "How can we reduce our exposure to this in the
future?"&lt;/p&gt;
&lt;p&gt;Any ideas?&lt;/p&gt;
&lt;h2&gt;Links&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="http://status.aws.amazon.com/"&gt;AWS Service Health Dashboard&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content><category term="Technology"></category><category term="amazon-web-services"></category><category term="aws"></category><category term="ebs"></category><category term="ec2"></category><category term="pbs"></category><category term="rds"></category></entry><entry><title>mod_wsgi and the HTTP Authorization header</title><link href="https://tomatohater.com/2011/04/07/mod_wsgi-and-the-http-authorization-header/" rel="alternate"></link><published>2011-04-07T15:39:00-07:00</published><updated>2011-04-07T15:39:00-07:00</updated><author><name>Drew Engelson</name></author><id>tag:tomatohater.com,2011-04-07:/2011/04/07/mod_wsgi-and-the-http-authorization-header/</id><summary type="html">&lt;p&gt;A friendly reminder for myself to RTFD!&lt;/p&gt;</summary><content type="html">&lt;p&gt;If one is building a WSGI application on mod_wsgi and one wants this
WSGI application to handle HTTP authentication, it is likely that one
will need access to the "Authorization" HTTP header at some point. Had
one read the documentation, one would have already understood that
mod_wsgi does not pass this header to your WSGI application by default.
This is probably a very good thing.&lt;/p&gt;
&lt;p&gt;To tell mod_wsgi to pass the Authorization header through to your
application, just set the mod_wsgi configuration directive
&lt;a href="http://code.google.com/p/modwsgi/wiki/ConfigurationDirectives#WSGIPassAuthorization"&gt;WSGIPassAuthorization&lt;/a&gt;
to "On".&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# enable authorization headers&lt;/span&gt;
&lt;span class="n"&gt;WSGIPassAuthorization&lt;/span&gt; &lt;span class="n"&gt;On&lt;/span&gt;

&lt;span class="c1"&gt;# set up wsgi app&lt;/span&gt;
&lt;span class="n"&gt;WSGIApplicationGroup&lt;/span&gt; &lt;span class="n"&gt;myapp&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="n"&gt;WSGIImportScript&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;projects&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;myapp&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;myapp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;wsgi&lt;/span&gt; &lt;span class="n"&gt;application&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;group&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;myapp&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="n"&gt;process&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;group&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;myapp&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="n"&gt;WSGIScriptAlias&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;myapp&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;projects&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;myapp&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;myapp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;wsgi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;| Note to self (and others who may have wasted time on such things):&lt;/p&gt;
&lt;p&gt;&lt;a href="http://code.google.com/p/modwsgi/wiki/ConfigurationDirectives"&gt;Read The
Docs!&lt;/a&gt;&lt;/p&gt;</content><category term="Technology"></category><category term="mod_wsgi"></category><category term="python"></category></entry><entry><title>AWS Identity and Access Management (IAM) with Python</title><link href="https://tomatohater.com/2011/01/22/aws-identity-and-access-management-iam-with-python/" rel="alternate"></link><published>2011-01-22T15:37:00-08:00</published><updated>2011-01-22T15:37:00-08:00</updated><author><name>Drew Engelson</name></author><id>tag:tomatohater.com,2011-01-22:/2011/01/22/aws-identity-and-access-management-iam-with-python/</id><summary type="html">&lt;p&gt;Flexible access control to AWS cloud services using Amazon IAM, Python, and boto&lt;/p&gt;</summary><content type="html">&lt;p&gt;With &lt;a href="http://aws.amazon.com/"&gt;all the AWS services&lt;/a&gt; that are now
available, our opportunities in the cloud are virtually unlimited. But
using any of these services requires access to your AWS_ACCESS_KEY_ID
and AWS_SECRET_ACCESS_KEY and unfortunately, these keys provides
complete access to the kingdom. This may not be a problem for some, but
for large enterprises, granular access control is a necessity.&lt;/p&gt;
&lt;p&gt;Up until recently, we would have been out of luck. But fortunately
Amazon released &lt;a href="http://aws.amazon.com/iam/"&gt;Identity and Access Management
(IAM)&lt;/a&gt; which makes flexible access control
possible. And &lt;a href="http://code.google.com/p/boto/"&gt;boto&lt;/a&gt; makes it easy in
&lt;a href="http://www.python.org"&gt;Python&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Take this example&lt;/h2&gt;
&lt;p&gt;Say your company uses Amazon S3 store your company's image assets in a
variety of S3 buckets. If you needed to grant a third party the ability
to upload new images to your S3 account, they will need a set of keys.
You wouldn't want to give them your main keys since not only would they
gain access to &lt;strong&gt;all&lt;/strong&gt; of your S3 buckets, but also your EC2 instances,
RDS databases, etc. Not a good situation.&lt;/p&gt;
&lt;p&gt;Here's where IAM comes in. With IAM, you can create a user for this
specific purpose which would have it's own unique AWS_ACCESS_KEY_ID and
AWS_SECRET_ACCESS_KEY key pair. Additionally, you can apply an IAM
policy to restrict what this user can do... a specific S3 bucket, in
this case.&lt;/p&gt;
&lt;h2&gt;Let's give it a try&lt;/h2&gt;
&lt;p&gt;In this example, we'll create a user called &lt;em&gt;melvins&lt;/em&gt; and grant it
access to an S3 bucket called &lt;em&gt;houdini&lt;/em&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;boto&lt;/span&gt;

&lt;span class="c1"&gt;# Connect to IAM with boto&lt;/span&gt;
&lt;span class="n"&gt;iam&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;boto&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;connect_iam&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;AWS_ACCESS_KEY_ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;AWS_SECRET_ACCESS_KEY&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Create user&lt;/span&gt;
&lt;span class="n"&gt;user_response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;iam&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create_user&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;melvins&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Limit access with IAM&lt;/span&gt;
&lt;span class="n"&gt;policypolicy_json&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;&amp;#39;&amp;#39;{&lt;/span&gt;
&lt;span class="s1"&gt;    &amp;quot;Statement&amp;quot;:[{&lt;/span&gt;
&lt;span class="s1"&gt;        &amp;quot;Sid&amp;quot;:&amp;quot;RandomStringIdentifier&amp;quot;,&lt;/span&gt;
&lt;span class="s1"&gt;        &amp;quot;Action&amp;quot;:&amp;quot;s3:*&amp;quot;,&lt;/span&gt;
&lt;span class="s1"&gt;        &amp;quot;Effect&amp;quot;:&amp;quot;Allow&amp;quot;,&lt;/span&gt;
&lt;span class="s1"&gt;        &amp;quot;Resource&amp;quot;:&amp;quot;arn:aws:s3:::houdini/*&amp;quot;&lt;/span&gt;
&lt;span class="s1"&gt;    }]&lt;/span&gt;
&lt;span class="s1"&gt;}&amp;#39;&amp;#39;&amp;#39;&lt;/span&gt;

&lt;span class="n"&gt;iam&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;put_user_policy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;melvins&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;allow_access_houdini&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;policy_json&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Generate new access key pair for &amp;#39;melvins&amp;#39;&lt;/span&gt;
&lt;span class="n"&gt;key_response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;iam&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create_access_key&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;melvins&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;We now have a new set of AWS keys with access limited to a single S3
bucket. We could have just as easily further limited it to specific
action (i.e. S3:PutObject), or to other services (i.e. EC2:*).&lt;/p&gt;
&lt;p&gt;The objects we created with boto provide us with all sorts of nifty
info.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;simplejson&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nn"&gt;json&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;user_response&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create_user_response&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create_user_result&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;print&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;indent&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="s2"&gt;&amp;quot;path&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;/&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;&amp;quot;create_date&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;2011-01-22T20:02:27.900Z&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;&amp;quot;user_id&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;AIDAXXXXXXXXXXXXXKXBW&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;&amp;quot;arn&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;arn:aws:iam::46XXXXXXXX90:user/melvins&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;&amp;quot;user_name&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;melvins&amp;quot;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;key_response&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create_access_key_response&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create_access_key_result&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;access_key&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;print&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;indent&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="s2"&gt;&amp;quot;status&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Active&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;&amp;quot;user_name&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;melvins&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;&amp;quot;create_date&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;2011-01-22T20:16:17.189Z&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;&amp;quot;secret_access_key&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;vDskXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX2cob&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;&amp;quot;access_key_id&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;AKIAXXXXXXXXXXXXQ5DA&amp;quot;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="c1"&gt;# Provide new keys to user&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;print&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;AWS_ACCESS_KEY_ID&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;access_key_id&lt;/span&gt;
&lt;span class="n"&gt;AWS_ACCESS_KEY_ID&lt;/span&gt; &lt;span class="n"&gt;AKIAXXXXXXXXXXXXQ5DA&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;print&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;AWS_SECRET_ACCESS_KEY&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;secret_access_key&lt;/span&gt;
&lt;span class="n"&gt;AWS_SECRET_ACCESS_KEY&lt;/span&gt; &lt;span class="n"&gt;vDskXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX2cob&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;Using IAM groups&lt;/h2&gt;
&lt;p&gt;Suppose your have three trusted employees (&lt;em&gt;Buzzo&lt;/em&gt;, &lt;em&gt;Lorax&lt;/em&gt; and &lt;em&gt;Dale&lt;/em&gt;)
who require full access to your AWS account. You could give them the
master key pair. But what if &lt;em&gt;Lorax&lt;/em&gt; decides to leave the company? You
would have to change your master key pair, redistribute to the remaining
employees, and hope there was nothing depending on the old ones. I would
grow tired of this very quickly.&lt;/p&gt;
&lt;p&gt;A better solution is to grant each employee his own key pair (as
described above). But rather than managing their policies individually,
you could add them to a group and apply a group policy.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;boto&lt;/span&gt;

&lt;span class="c1"&gt;# Connect to IAM with boto&lt;/span&gt;
&lt;span class="n"&gt;iam&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;boto&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;connect_iam&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;AWS_ACCESS_KEY_ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;AWS_SECRET_ACCESS_KEY&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Create group&lt;/span&gt;
&lt;span class="n"&gt;iam&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create_group&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;admins&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Apply full access IAM policy&lt;/span&gt;
&lt;span class="n"&gt;policy_json&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;&amp;#39;&amp;#39;{&lt;/span&gt;
&lt;span class="s1"&gt;    &amp;quot;Statement&amp;quot;:[{&lt;/span&gt;
&lt;span class="s1"&gt;        &amp;quot;Effect&amp;quot;:&amp;quot;Allow&amp;quot;,&lt;/span&gt;
&lt;span class="s1"&gt;        &amp;quot;Action&amp;quot;:&amp;quot;*&amp;quot;,&lt;/span&gt;
&lt;span class="s1"&gt;        &amp;quot;Resource&amp;quot;:&amp;quot;*&amp;quot;&lt;/span&gt;
&lt;span class="s1"&gt;    }&lt;/span&gt;
&lt;span class="s1"&gt;]}&amp;#39;&amp;#39;&amp;#39;&lt;/span&gt;
&lt;span class="n"&gt;iam&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;put_group_policy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;admins&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;allow_all&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;policy_json&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;#Create users&lt;/span&gt;
&lt;span class="n"&gt;iam&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create_user&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;buzzo&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;iam&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create_user&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;lorax&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;iam&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create_user&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;dale&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Generate access keys&lt;/span&gt;
&lt;span class="n"&gt;iam&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create_access_key&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;buzzo&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;iam&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create_access_key&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;lorax&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;iam&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create_access_key&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;dale&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Add users to group&lt;/span&gt;
&lt;span class="n"&gt;iam&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add_user_to_group&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;admins&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;buzzo&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;iam&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add_user_to_group&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;admins&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;lorax&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;iam&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add_user_to_group&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;admins&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;dale&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;So when &lt;em&gt;Lorax&lt;/em&gt; takes off, it's just a simple matter.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;iam&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;remove_user_from_group&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;admins&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;lorax&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;iam&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;delete_user&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;lorax&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The moral of the story is: Quit distributing your master access keys.
Use IAM. I doubt you want to learn this lesson the hard way!&lt;/p&gt;
&lt;p&gt;Version note: boto added IAM support in 2.0b3.&lt;/p&gt;
&lt;h2&gt;Links&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="http://aws.amazon.com/iam/"&gt;AWS Identity and Access Management
    (IAM)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://boto.cloudhackers.com/ref/iam.html"&gt;boto v2.0 &gt; API Reference &gt;
    IAM&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.last.fm/music/Melvins"&gt;Melvins&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content><category term="Technology"></category><category term="amazon-web-services"></category><category term="aws"></category><category term="boto"></category><category term="cloud"></category><category term="iam"></category><category term="python"></category></entry><entry><title>pyawschart - v0.2 released</title><link href="https://tomatohater.com/2011/01/10/pyawschart-v02-released/" rel="alternate"></link><published>2011-01-10T15:35:00-08:00</published><updated>2011-01-10T15:35:00-08:00</updated><author><name>Drew Engelson</name></author><id>tag:tomatohater.com,2011-01-10:/2011/01/10/pyawschart-v02-released/</id><summary type="html">&lt;p&gt;Amazon CloudWatch data visualization&lt;/p&gt;</summary><content type="html">&lt;p&gt;I have just pushed the source code for &lt;a href="/pyawschart/"&gt;pyawschart - v0.2&lt;/a&gt;
out to &lt;a href="http://github.com/tomatohater/pyawschart"&gt;GitHub&lt;/a&gt;. This project
was created a few months back and I have been using it for personal
(&lt;a href="http://probosc.is/"&gt;Proboscis&lt;/a&gt;) and professional
(&lt;a href="http://www.pbs.org/"&gt;PBS&lt;/a&gt;) projects since then. I've just decided to
open source it for the betterment of the AWS and Python communities.&lt;/p&gt;
&lt;p&gt;The goal was quite simple... Use &lt;a href="http://boto.cloudhackers.com/"&gt;boto&lt;/a&gt;
to pipe &lt;a href="http://aws.amazon.com/cloudwatch/"&gt;Amazon CloudWatch&lt;/a&gt; data into
&lt;a href="http://pygooglechart.slowchop.com/"&gt;Python Google Chart&lt;/a&gt; and hope for
some really cool data visualizations.&lt;/p&gt;
&lt;p&gt;The results are pretty neat:&lt;/p&gt;
&lt;h2&gt;Features&lt;/h2&gt;
&lt;p&gt;&lt;a href="/pyawschart/"&gt;pyawschart - v0.2&lt;/a&gt; supports all of the metrics that
Amazon makes available for Elastic Compute Cloud (EC2), Relation
Database Service (RDS), Elastic Block Store (EBS), and Elastic Load
Balancer (ELB).&lt;/p&gt;
&lt;h2&gt;Example code&lt;/h2&gt;
&lt;p&gt;Get EC2 cpu utilization chart for the past hour.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;boto&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;pyawschart&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;RANGES&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;pyawschart.ec2&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;CPUUtilizationChart&lt;/span&gt;

&lt;span class="c1"&gt;# Connect with botoconn = boto.connect_cloudwatch(AWS_ACCESS_KEY AWS_SECRET_ACCCES_KEY)&lt;/span&gt;
&lt;span class="n"&gt;chart&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;CPUUtilizationChart&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;i-6d3efXXX&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;RANGES&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;hour&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

&lt;span class="c1"&gt;# Display chart url&lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt; &lt;span class="n"&gt;chart&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_url&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="c1"&gt;# Download chart image&lt;/span&gt;
&lt;span class="n"&gt;chart&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;download&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;/tmp/i-6d3efXXX-cpu-hour.png&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Get RDS database connections chart for the past day.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;boto&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;pyawschart&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;RANGES&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;pyawschart.rds&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;DatabaseConnectionsChart&lt;/span&gt;

&lt;span class="c1"&gt;# Connect with botoconn = boto.connect_cloudwatch(AWS_ACCESS_KEY AWS_SECRET_ACCCES_KEY)&lt;/span&gt;
&lt;span class="n"&gt;chart&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;DatabaseConnectionsChart&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;my-rds-instance&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;RANGES&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;day&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

&lt;span class="c1"&gt;# Display chart url&lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt; &lt;span class="n"&gt;chart&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_url&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="c1"&gt;# Download chart image&lt;/span&gt;
&lt;span class="n"&gt;chart&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;download&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;/tmp/my-ec2-instance-connections-day.png&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;Feedback&lt;/h2&gt;
&lt;p&gt;While the library works well for most of the use cases I require, I
would welcome feedback on your use cases. Use it... Abuse it.. then
submit comments on this entry, or post issues/requests on
&lt;a href="https://github.com/tomatohater/pyawschart/issues"&gt;GitHub&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;URLs&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Project page: &lt;a href="http://tomatohater.com/pyawschart/"&gt;http://tomatohater.com/pyawschart/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Source code: &lt;a href="http://github.com/tomatohater/pyawschart"&gt;http://github.com/tomatohater/pyawschart&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Issue tracker: &lt;a href="http://github.com/tomatohater/pyawschart/issues"&gt;http://github.com/tomatohater/pyawschart/issues&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content><category term="Technology"></category><category term="amazon-web-services"></category><category term="aws"></category><category term="boto"></category><category term="cloud"></category><category term="cloudwatch"></category><category term="ebs"></category><category term="ec2"></category><category term="elb"></category><category term="python"></category><category term="rds"></category></entry><entry><title>New PBS.org launched!</title><link href="https://tomatohater.com/2010/10/25/new-pbsorg-launched/" rel="alternate"></link><published>2010-10-25T15:32:00-07:00</published><updated>2010-10-25T15:32:00-07:00</updated><author><name>Drew Engelson</name></author><id>tag:tomatohater.com,2010-10-25:/2010/10/25/new-pbsorg-launched/</id><summary type="html">&lt;p&gt;New Django-based PBS.org site and APIs&lt;/p&gt;</summary><content type="html">&lt;p&gt;Today is a big day. Just after midnight we pulled the trigger and
launched the new re-envisioned &lt;a href="http://www.pbs.org/"&gt;PBS.org website&lt;/a&gt;.
This is a huge step for PBS on a number of fronts; mostly having to do
bringing PBS to the &lt;a href="http://www.hollywoodreporter.com/news/pbs-making-digital-push-new-32272%20title="&gt;forefront of new
media&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;But for me, as you might imagine, the most interesting aspect is the
technology architecture underlying it all.&lt;/p&gt;
&lt;h2&gt;A win for Django and Python&lt;/h2&gt;
&lt;p&gt;There is no doubt that the new PBS.org site probably ranks as one of the
most highly trafficked Django sites in existence today. I think there is
a lot to be said about how Django may be scaled if an application is
properly designed. The tools are all there for the taking. I will write
a future post that details the full architecture, but here is a short
list of technologies involved in the stack:
&lt;a href="http://python.org/"&gt;Python&lt;/a&gt;, &lt;a href="http://www.djangoproject.com/"&gt;Django&lt;/a&gt;,
&lt;a href="http://www.mysql.com/"&gt;MySQL&lt;/a&gt;, &lt;a href="http://httpd.apache.org/"&gt;Apache&lt;/a&gt;,
&lt;a href="http://memcached.org/"&gt;Memcached&lt;/a&gt;, &lt;a href="http://celeryproject.org/"&gt;Celery&lt;/a&gt;,
&lt;a href="http://bitbucket.org/jespern/django-piston/wiki/Home"&gt;Piston&lt;/a&gt;, &lt;a href="http://aws.amazon.com/"&gt;Amazon
Web Services&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;And thanks for cloud computing...&lt;/h2&gt;
&lt;p&gt;You may have noticed Amazon Web Services mentioned above. I slipped it
in at the end of the list, but in reality, it is truly the enabling
technology that makes all else possible. The new PBS.org site sits on a
cluster of Amazon EC2 instances and is powered by a few Relational
Database Service (RDS) instances. In my opinion, RDS is already very
cool, and getting much cooler.&lt;/p&gt;
&lt;p&gt;Putting the applications in the cloud lets us be very agile on the
infrastructure side. In fact, just prior to launch, we decided to put
the cache on a bigger machine and that took all of 10 minutes. This kind
of stuff is just not possible on a traditional infrastructure. Thank
you, Amazon.&lt;/p&gt;
&lt;p&gt;And big thanks to the entire team of smart and hardworking individuals
that made this site launch a success. Hats off to you all!&lt;/p&gt;</content><category term="Technology"></category><category term="amazon-web-services"></category><category term="aws"></category><category term="django"></category><category term="ec2"></category><category term="pbs"></category><category term="python"></category><category term="rds"></category></entry><entry><title>CloudFront object invalidation in Python</title><link href="https://tomatohater.com/2010/10/22/cloudfront-object-invalidation-in-python/" rel="alternate"></link><published>2010-10-22T15:31:00-07:00</published><updated>2010-10-22T15:31:00-07:00</updated><author><name>Drew Engelson</name></author><id>tag:tomatohater.com,2010-10-22:/2010/10/22/cloudfront-object-invalidation-in-python/</id><summary type="html">&lt;p&gt;Pythonically purge objects from CloudFront via RESTful API&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;a href="http://aws.amazon.com"&gt;Amazon Web Services&lt;/a&gt; enables the management of
all AWS services through some pretty powerful REST APIs.
&lt;a href="http://aws.amazon.com/cloudfront/"&gt;CloudFront&lt;/a&gt; is Amazon's content
delivery network offering which allows you to serve files out of S3
buckets with much better performance. S3 files are cached at edge
servers so that users may download them more quickly. The service plays
nice with cache-control and expiration headers, but what happens when
you need to delete or update some cached files from the edge
immediately?&lt;/p&gt;
&lt;p&gt;CloudFront &lt;a href="http://docs.amazonwebservices.com/AmazonCloudFront/latest/APIReference/index.html?CreateInvalidation.html"&gt;object
invalidation&lt;/a&gt;
is a recent addition... here's how to do it in Python.&lt;/p&gt;
&lt;h2&gt;AWS REST authentication&lt;/h2&gt;
&lt;p&gt;One of the first things you'll need to do for each request is &lt;a href="http://docs.amazonwebservices.com/AmazonCloudFront/latest/DeveloperGuide/RESTAuthentication.html"&gt;generate
an authentication
signature&lt;/a&gt;
based on your AWS credentials. So let's define a function for that.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;base64&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;datetime&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;hmac&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;hashlib&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_aws_auth&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;date&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;utcnow&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;strftime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="si"&gt;%a&lt;/span&gt;&lt;span class="s1"&gt;, &lt;/span&gt;&lt;span class="si"&gt;%d&lt;/span&gt;&lt;span class="s1"&gt; %b %Y %H:%M:%S GMT&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;auth&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;AWS &lt;/span&gt;&lt;span class="si"&gt;%s&lt;/span&gt;&lt;span class="s1"&gt;:&lt;/span&gt;&lt;span class="si"&gt;%s&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;AWS_ACCESS_KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;base64&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;b64encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;hmac&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;AWS_SECRET_ACCESS_KEY&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;utf-8&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;utf-8&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="n"&gt;hashlib&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sha1&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;digest&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;auth&lt;/span&gt;

&lt;span class="nb"&gt;print&lt;/span&gt; &lt;span class="n"&gt;get_aws_auth&lt;/span&gt;&lt;span class="p"&gt;()(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Thu, 22 Oct 2010 06:08:48 GMT&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;AWS 0PN5J17HBGZHT7JJ3X82:4cP0hCJsdCxTJ1jPXo7+e/YSu0g=&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;We can now use &lt;em&gt;get_aws_auth()&lt;/em&gt; to sign the following REST requests.
Next, we'll create an invalidation request.&lt;/p&gt;
&lt;h2&gt;Invalidating CloudFront objects&lt;/h2&gt;
&lt;p&gt;The invalidation handler expects a POST request with an xml payload
containing a list of file paths to be expired.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;urllib2&lt;/span&gt;

&lt;span class="n"&gt;host&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;cloudfront.amazonaws.com&amp;#39;&lt;/span&gt;
&lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;/2010-08-01/distribution/&lt;/span&gt;&lt;span class="si"&gt;%s&lt;/span&gt;&lt;span class="s1"&gt;/invalidation&amp;#39;&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;AWS_CF_DISTRIBUTION_ID&lt;/span&gt;

&lt;span class="c1"&gt;# create authentication signature&lt;/span&gt;
&lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;auth&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;get_aws_auth&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="n"&gt;files&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;/file-01.mp4&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;/file-02.mp4&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;/file-03.mp4&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="c1"&gt;# create xml data to post&lt;/span&gt;
&lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot;?&amp;gt;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&amp;lt;InvalidationBatch&amp;gt;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="si"&gt;%s&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;join&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&amp;lt;Path&amp;gt;/&lt;/span&gt;&lt;span class="si"&gt;%s&lt;/span&gt;&lt;span class="s1"&gt;&amp;lt;/Path&amp;gt;&amp;#39;&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;files&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt;
&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&amp;lt;CallerReference&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;%s&lt;/span&gt;&lt;span class="s1"&gt;&amp;lt;/CallerReference&amp;gt;&amp;#39;&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&amp;lt;/InvalidationBatch&amp;gt;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# define required http headers&lt;/span&gt;
&lt;span class="n"&gt;headers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;Host&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;Authorization&amp;#39;&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;Date&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;Content-Type&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;text/xml&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;Content-Length&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# make REST request, capture 201 (success) response&lt;/span&gt;
&lt;span class="n"&gt;request_url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;https://&lt;/span&gt;&lt;span class="si"&gt;%s%s&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;req&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;urllib2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request_url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;urllib2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;urlopen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;urllib2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HTTPError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nb"&gt;print&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;HTTP Error 201: Created&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Of course, you probably also want to check the status of your
invalidation request&lt;/p&gt;
&lt;h2&gt;Retrieving invalidation request status&lt;/h2&gt;
&lt;p&gt;Invalidation requests can take up to 15 minutes. Hit the GET
invalidation API to check on the status of your recent requests.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;urllib2&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;xml.dom&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;minidom&lt;/span&gt;

&lt;span class="n"&gt;host&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;cloudfront.amazonaws.com&amp;#39;&lt;/span&gt;
&lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;/2010-08-01/distribution/&lt;/span&gt;&lt;span class="si"&gt;%s&lt;/span&gt;&lt;span class="s1"&gt;/invalidation?MaxItems=2&amp;#39;&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;AWS_CF_DISTRIBUTION_ID&lt;/span&gt;

&lt;span class="c1"&gt;# create authentication signature&lt;/span&gt;
&lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;auth&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;get_aws_auth&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="c1"&gt;# define required http headers&lt;/span&gt;
&lt;span class="n"&gt;headers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;Host&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;Authorization&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;Date&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# send REST request and print results&lt;/span&gt;
&lt;span class="n"&gt;request_url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;https://&lt;/span&gt;&lt;span class="si"&gt;%s%s&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;req&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;urllib2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request_url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;urllib2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;urlopen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;xml&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt; &lt;span class="n"&gt;minidom&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parseString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;xml&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;toprettyxml&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;indent&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39; &amp;#39;&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;?&lt;/span&gt;&lt;span class="n"&gt;xml&lt;/span&gt; &lt;span class="n"&gt;version&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;1.0&amp;quot;&lt;/span&gt; &lt;span class="err"&gt;?&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;InvalidationList&lt;/span&gt; &lt;span class="n"&gt;xmlns&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;http://cloudfront.amazonaws.com/doc/2010-08-01/&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Marker&lt;/span&gt;&lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;NextMarker&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="n"&gt;I1Z3T4GFYRIAZU&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;NextMarker&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;MaxItems&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="mi"&gt;2&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;MaxItems&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IsTruncated&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="n"&gt;true&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;IsTruncated&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;InvalidationSummary&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Id&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="n"&gt;I2HD8ADHMH7R&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;Id&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Status&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="n"&gt;InProgress&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;Status&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;InvalidationSummary&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;InvalidationSummary&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Id&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="n"&gt;IEFHBD78AAQN&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;Id&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Status&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="n"&gt;Completed&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;Status&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;InvalidationSummary&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;InvalidationList&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;The easy way: Use boto&lt;/h2&gt;
&lt;p&gt;As of version 2.0b3, the kick-ass &lt;a href="http://code.google.com/p/boto/"&gt;boto&lt;/a&gt;
library (Python interface to Amazon Web Services) has added support for
invalidating CloudFront objects.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;boto.cloudfront&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;CloudFrontConnection&lt;/span&gt;

&lt;span class="n"&gt;files&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;/file-01.mp4&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;/file-02.mp4&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;/file-03.mp4&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="n"&gt;conn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;CloudFrontConnection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;AWS_ACCESS_KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;AWS_SECRET_ACCESS_KEY&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt; &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create_invalidation_request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;AWS_CF_DISTRIBUTION_ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;files&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;boto&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cloudfront&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;invalidation&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;InvalidationBatch&lt;/span&gt; &lt;span class="nb"&gt;object&lt;/span&gt; &lt;span class="n"&gt;at&lt;/span&gt; &lt;span class="mh"&gt;0x1012b6390&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;So short and sweet.&lt;/p&gt;</content><category term="Technology"></category><category term="amazon-web-services"></category><category term="api"></category><category term="aws"></category><category term="boto"></category><category term="cdn"></category><category term="cloudfront"></category><category term="expiration"></category><category term="invalidation"></category><category term="python"></category><category term="rest"></category></entry><entry><title>Pretty print xml (and json) in Python</title><link href="https://tomatohater.com/2010/10/20/pretty-print-xml-and-json-in-python/" rel="alternate"></link><published>2010-10-20T15:29:00-07:00</published><updated>2010-10-20T15:29:00-07:00</updated><author><name>Drew Engelson</name></author><id>tag:tomatohater.com,2010-10-20:/2010/10/20/pretty-print-xml-and-json-in-python/</id><summary type="html">&lt;p&gt;Quick Python one-liner to prettify xml (and json too)&lt;/p&gt;</summary><content type="html">&lt;p&gt;So that I don't forget... again.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;xml.dom&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;minidom&lt;/span&gt;

&lt;span class="c1"&gt;# from a string&lt;/span&gt;
&lt;span class="n"&gt;minidom&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parseString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;xml_string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;toprettyxml&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;indent&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39; &amp;#39;&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# or from a file&lt;/span&gt;
&lt;span class="n"&gt;minidom&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;xml_file&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;toprettyxml&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;indent&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39; &amp;#39;&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;And for the record...&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;simplejson&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nn"&gt;json&lt;/span&gt;

&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;json_obj&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;indent&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</content><category term="Technology"></category><category term="code"></category><category term="json"></category><category term="prettify"></category><category term="python"></category><category term="xml"></category></entry><entry><title>The NPR Radio by Livio</title><link href="https://tomatohater.com/2010/10/07/the-npr-radio-by-livio/" rel="alternate"></link><published>2010-10-07T15:28:00-07:00</published><updated>2010-10-07T15:28:00-07:00</updated><author><name>Drew Engelson</name></author><id>tag:tomatohater.com,2010-10-07:/2010/10/07/the-npr-radio-by-livio/</id><summary type="html">&lt;p&gt;What's the verdict?&lt;/p&gt;</summary><content type="html">&lt;p&gt;I was at Micro Center today and came across this neat looking device
called &lt;a href="http://shop.npr.org/products/The_NPR_Radio_by_Livio-906-0.html"&gt;The NPR
Radio&lt;/a&gt;
by &lt;a href="http://www.livioradio.com/npr_radio/"&gt;Livio&lt;/a&gt;. My first reaction was,
"Wow! This is so cool. You can listen to NPR anytime, anywhere! Why
aren't we selling The PBS Television yet?" or something along those
lines.&lt;/p&gt;
&lt;p&gt;But upon thinking about this for a few moments, it occurred to me that
my regular radio already gets NPR, costs less than $199, and it
doesn't even require an internet connection.&lt;/p&gt;</content><category term="Technology"></category><category term="internet-radio"></category><category term="npr"></category><category term="pbs"></category></entry><entry><title>Private repos on BitBucket</title><link href="https://tomatohater.com/2010/10/02/private-repos-on-bitbucket/" rel="alternate"></link><published>2010-10-02T15:27:00-07:00</published><updated>2010-10-02T15:27:00-07:00</updated><author><name>Drew Engelson</name></author><id>tag:tomatohater.com,2010-10-02:/2010/10/02/private-repos-on-bitbucket/</id><summary type="html">&lt;p&gt;Mercury rising?&lt;/p&gt;</summary><content type="html">&lt;p&gt;Well now they've done it... With the &lt;a href="http://blogs.atlassian.com/news/2010/09/weve_gone_into_the_code_hosting_business.html"&gt;acquisition of
BitBucket&lt;/a&gt;
by &lt;a href="http://www.atlassian.com/"&gt;Atlassian&lt;/a&gt;, we can expect
&lt;a href="http://www.bitbucket.org/"&gt;BitBucket&lt;/a&gt; to see tremendous gains in
usability and developer support. They now offer unlimited public and
private repositories for up to 5 users for free.&lt;/p&gt;
&lt;p&gt;Will users flock to Mercurial leaving Git behind? I say, "Hello hg!"&lt;/p&gt;</content><category term="Technology"></category><category term="bitbucket"></category><category term="code"></category><category term="hg"></category><category term="version-control"></category></entry><entry><title>Private clouds for developers</title><link href="https://tomatohater.com/2010/09/23/private-clouds-for-developers/" rel="alternate"></link><published>2010-09-23T15:24:00-07:00</published><updated>2010-09-23T15:24:00-07:00</updated><author><name>Drew Engelson</name></author><id>tag:tomatohater.com,2010-09-23:/2010/09/23/private-clouds-for-developers/</id><summary type="html">&lt;p&gt;A viable option?&lt;/p&gt;</summary><content type="html">&lt;p&gt;At PBS we have been launching most new applications up in &lt;a href="http://aws.amazon.com/ec2/"&gt;Amazon's
cloud platform&lt;/a&gt; utilizing EC2 instances, EBS
and S3 storage, etc. The flexibility and agility that this
Infrastructure as a Service (IaaS) is truly game changing, if not
frightening (to IT departments).&lt;/p&gt;
&lt;p&gt;The ease with which I am able spin up a server or 30 still gives me the
chills even though I've been doing this for years by now. In fact, it
is so easy that in addition to launching production applications in the
cloud, we've been providing developers each their own dedicated EC2
servers for the applications they are building, modifying, or testing.
The benefits of doing so are easy to see:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Development environment is &lt;strong&gt;identical in all aspects&lt;/strong&gt; to
    production environment.&lt;/li&gt;
&lt;li&gt;Deployment systems (such as Fabric) can be used to push to QA,
    staging and production servers as easily as to the developers
    instance.&lt;/li&gt;
&lt;li&gt;Instances can be easily cloned if something goes wrong. Made a
    mistake on configuring the environment? Who cares? Blow it away and
    start over.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;But is it too easy?&lt;/h2&gt;
&lt;p&gt;For each active application, we may have a dozen or so developers, each
with their own EC2 instance. As more applications and developers come on
board, you can foresee how we might end up with multiplicative growth in
the number of development servers. Let's take 10 developers each
working on 3 projects... that's 30 development instances for 3
projects. And that's not counting QA, staging or production instances.
Since a throw-away development instance costs just as much as a mission
critical production instance... the cost balance feels out of whack to
me.&lt;/p&gt;
&lt;p&gt;Is there a way to bring down the cost of development instances while
maintaining the above-mentioned benefits? What about hosting development
servers in a private AWS compatible cloud, such as those fronted by
&lt;a href="http://www.eucalyptus.com/"&gt;Eucalyptus&lt;/a&gt;? There may be potential here
and we are prototyping this out. But I suspect the underlying hardware
required to run such a private cloud would be as costly (and less
flexible) than using dedicated AWS EC2 instances.&lt;/p&gt;
&lt;p&gt;Amazon's new &lt;a href="http://aws.amazon.com/about-aws/whats-new/2010/09/09/announcing-micro-instances-for-amazon-ec2/"&gt;micro
instances&lt;/a&gt;
should provide some relief. Any other ideas?&lt;/p&gt;</content><category term="Technology"></category><category term="amazon-web-services"></category><category term="aws"></category><category term="cloud"></category><category term="ec2"></category></entry><entry><title>Future of Faetus...</title><link href="https://tomatohater.com/2010/07/21/future-of-faetus/" rel="alternate"></link><published>2010-07-21T15:21:00-07:00</published><updated>2010-07-21T15:21:00-07:00</updated><author><name>Drew Engelson</name></author><id>tag:tomatohater.com,2010-07-21:/2010/07/21/future-of-faetus/</id><summary type="html">&lt;p&gt;Should my FTP-to-S3 project evolve?&lt;/p&gt;</summary><content type="html">&lt;p&gt;In my recent post entitled, &lt;a href="http://tomatohater.com/2010/07/15/faetus-v05-released/"&gt;Faetus v0.5
released!&lt;/a&gt;, I
described how and why I built a Python-based FTP server that reads and
writes to Amazon S3. This project was born out of necessity because I am
using a 3rd-party file management application (which didn't understand
the S3 API) but needed the files to end up on S3. The system did talk
FTP, so I wrote an FTP server that does just that.&lt;/p&gt;
&lt;p&gt;As it turns out, the file manager DOES now support the S3 API. Therefore
my immediate need for an intermediary FTP interface is gone. While I
think it is still a very cool idea, I no longer have an urgent need for
it. I will continue to fix some of the major bugs with it, but most of
my tinkering will cease. Please let me know if you are interested in
using &lt;a href="http://tomatohater.com/faetus/"&gt;Faetus&lt;/a&gt; and I'm happy to help
you out if I can.&lt;/p&gt;</content><category term="Technology"></category><category term="amazon-web-services"></category><category term="cloud"></category><category term="faetus"></category><category term="ftp"></category><category term="python"></category><category term="s3"></category></entry><entry><title>Faetus v0.5 released!</title><link href="https://tomatohater.com/2010/07/15/faetus-v05-released/" rel="alternate"></link><published>2010-07-15T15:20:00-07:00</published><updated>2010-07-15T15:20:00-07:00</updated><author><name>Drew Engelson</name></author><id>tag:tomatohater.com,2010-07-15:/2010/07/15/faetus-v05-released/</id><summary type="html">&lt;p&gt;An FTP interface to Amazon S3 file storage.&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;a href="/faetus/"&gt;Faetus&lt;/a&gt; is an FTP server that translates FTP commands into
Amazon S3 API calls providing an FTP interface on top of Amazon S3
storage.&lt;/p&gt;
&lt;p&gt;This project came out of a specific need I have at PBS. I need to put
files on Amazon S3 storage, but the 3rd-party file management system
doesn't know how to speak to S3's APIs. What the system does
understand is FTP. So I decided to write an FTP server that utilizes S3
storage as opposed to local filesystems.&lt;/p&gt;
&lt;p&gt;The server is based on &lt;a href="http://code.google.com/p/pyftpdlib/"&gt;pyftpdlib&lt;/a&gt;,
a Python library that implements most, if not the full RFC spec for.
Especially nice is the fact that the the library implements the
filesystem layer in an abstract fashion. I basically needed to replace
all the filesystem calls with S3 API calls. I used
&lt;a href="http://code.google.com/p/boto/"&gt;boto&lt;/a&gt; to handle all the S3 API calls
and I quickly had a working prototype.&lt;/p&gt;
&lt;p&gt;After playing with my prototype for a few hours, I stumbled upon Chmouel's
&lt;a href="http://blog.chmouel.com/2009/10/29/ftp-server-for-cloud-files/"&gt;ftp-cloudfs&lt;/a&gt;...
who basically did the exact thing I had just done, but for Rackspace
CloudFiles. For consistency's sake, I rolled my code back, forked
ftp-cloudfs, gave the project a questionable new name, and added my S3
code back in. La voila!&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="/faetus/"&gt;Faetus&lt;/a&gt; project page&lt;/li&gt;
&lt;li&gt;&lt;a href="http://github.com/tomatohater/faetus"&gt;Source code&lt;/a&gt; on GitHub&lt;/li&gt;
&lt;/ul&gt;</content><category term="Technology"></category><category term="amazon-web-services"></category><category term="cloud"></category><category term="faetus"></category><category term="ftp"></category><category term="python"></category><category term="s3"></category></entry><entry><title>GoogleCL is serious life automation</title><link href="https://tomatohater.com/2010/06/21/googlecl-is-serious-life-automation/" rel="alternate"></link><published>2010-06-21T15:19:00-07:00</published><updated>2010-06-21T15:19:00-07:00</updated><author><name>Drew Engelson</name></author><id>tag:tomatohater.com,2010-06-21:/2010/06/21/googlecl-is-serious-life-automation/</id><summary type="html">&lt;p&gt;Command line tools for the Google Data APIs&lt;/p&gt;</summary><content type="html">&lt;p&gt;Google released &lt;a href="http://code.google.com/p/googlecl/"&gt;GoogleCL&lt;/a&gt; last
Friday they describe as "Command line tools for the Google Data APIs".
I took a few minutes to play with it today and an awed by the
possibilities it opens up. Yes, APIs have existed for all these
services, so in theory, it's nothing new... But really, who wants to
write command-line wrappers for the APIs, or manage all the different
3rd party packages for doing so.&lt;/p&gt;
&lt;p&gt;It's basically a bunch of Python scripts that are all imported into one
executable file called "google". All the API calls are full abstracted
allowing you do run commands like:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ google contact list
$ google calendar add &lt;span class="s2"&gt;&amp;quot;Get up at 5:30am Thursday&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;GoogleCL currently supports Blogger, Calendar, Contacts, Docs, Picasa,
YouTube... though I'm sure others will be added shortly.&lt;/p&gt;
&lt;p&gt;Installing is pretty much a piece of cake. All you really need is a
modern version of the gdata library (mine was outdated) and the googlecl
package itself.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ pip install gdata googlecl
$ pip install
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;I see some serious life automation happening in the near future.&lt;/p&gt;</content><category term="Technology"></category><category term="command-line"></category><category term="google"></category></entry><entry><title>django-openlike v0.1</title><link href="https://tomatohater.com/2010/05/20/django-openlike-v01/" rel="alternate"></link><published>2010-05-20T15:17:00-07:00</published><updated>2010-05-20T15:17:00-07:00</updated><author><name>Drew Engelson</name></author><id>tag:tomatohater.com,2010-05-20:/2010/05/20/django-openlike-v01/</id><summary type="html">&lt;p&gt;A Django template tag for easily implementing and extending OpenLike protocol.&lt;/p&gt;</summary><content type="html">&lt;p&gt;| Source code available at GitHub:&lt;/p&gt;
&lt;p&gt;&lt;a href="http://github.com/tomatohater/django-openlike"&gt;http://github.com/tomatohater/django-openlike&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;Release notes (v0.1):&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;# django-openlike
Django template tag for easily implementing and extending OpenLike protocol.

See http://www.openlike.org/ for more info.

FEATURES:
    - Templatetags for displaying OpenLike widget

USAGE:
    - Basic: {% openlike %}
    - Less basic: {% openlike &amp;quot;http://domain.com/&amp;quot; &amp;quot;Page title&amp;quot; %}

TODO:
    - Documentation.
    - Add support for extended sources.
    - Configuration via Django admin

AUTHOR&amp;#39;S NOTE:

This app is most definitely overkill given the simplicity of OpenLike and the ease of pure JavaScript implementation. But it was a fun project to fully support a third-party service in Django.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</content><category term="Technology"></category><category term="bookmarking"></category><category term="django"></category><category term="open-source"></category><category term="openlike"></category><category term="python"></category><category term="social-networking"></category></entry><entry><title>My slides from RightScale MeetUp 2010</title><link href="https://tomatohater.com/2010/05/19/my-slides-from-rightscale-meetup-2010/" rel="alternate"></link><published>2010-05-19T15:15:00-07:00</published><updated>2010-05-19T15:15:00-07:00</updated><author><name>Drew Engelson</name></author><id>tag:tomatohater.com,2010-05-19:/2010/05/19/my-slides-from-rightscale-meetup-2010/</id><summary type="html">&lt;p&gt;Last month, I presented on how PBS utilizes RightScale to manage our
servers in the Amazon Cloud.&lt;/p&gt;</summary><content type="html">&lt;p&gt;This is my deck from &lt;a href="https://www.slideshare.net/tomatohater/pbs-rightscale-user-meetup-2010"&gt;RightScale MeetUp
2010&lt;/a&gt; before it was
incorporated into RightScale's template. The &lt;a href="http://www.rightscale.com/lp/downloads/RightScale-User-Meetup-2010.pdf"&gt;full MeetUp
presentation&lt;/a&gt;
is up on RightScale's website.&lt;/p&gt;
&lt;p&gt;At the MeetUp, I met a ton of really smart people with interesting
problems to solve... many with the same problems I'm working on as
well.&lt;/p&gt;</content><category term="Technology"></category><category term="amazon-web-services"></category><category term="cloud"></category><category term="pbs"></category><category term="rightscale"></category></entry><entry><title>Talking points: Django v. Drupal</title><link href="https://tomatohater.com/2010/05/13/talking-points-django-v-drupal/" rel="alternate"></link><published>2010-05-13T15:13:00-07:00</published><updated>2010-05-13T15:13:00-07:00</updated><author><name>Drew Engelson</name></author><id>tag:tomatohater.com,2010-05-13:/2010/05/13/talking-points-django-v-drupal/</id><summary type="html">&lt;p&gt;Why you should use Django instead of Drupal.&lt;/p&gt;</summary><content type="html">&lt;p&gt;At &lt;a href="http://www.pbs.org/"&gt;PBS&lt;/a&gt;, I frequently find myself with a proposal
to build a web application with Drupal. We're primarily a
&lt;a href="http://www.djangoproject.com/"&gt;Django&lt;/a&gt; shop, so you can imagine my bias
against Drupal. Yet these proposals continue and I routinely find myself
pushing Django instead.&lt;/p&gt;
&lt;p&gt;To date, we have attempted exactly four Drupal sites at PBS. Three of
them eventually made it to production. All three experienced significant
delivery, quality assurance and/or scalability problems. These apps were
built by experienced development shops, even some who had proven track
records and delivered exceptional (non-Drupal) applications in the past.&lt;/p&gt;
&lt;p&gt;I am sure that it is possible to build a solid, highly scalable Drupal
application. There are many examples out in the world. So what's the
deal?&lt;/p&gt;
&lt;p&gt;In a recent post, &lt;a href="http://nicksergeant.com/2010/drupal-v-django/"&gt;Drupal v.
Django&lt;/a&gt;, Nick Sergeant
provides some excellent talking points on the matter. I agree with every
one of his points. Some of my favorites are echoed below:&lt;/p&gt;
&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;Django is a lower level development framework (than Drupal) that
    encourages rapid development and cleanly written &amp;amp; organized code.&lt;/li&gt;
&lt;li&gt;With Django, we're able to construct custom functionality at a
    more rapid pace than Drupal.&lt;/li&gt;
&lt;li&gt;Drupal funnels data into chunks of content referred to as "nodes",
    which developers must often force their content into. The content
    should define the data structure, not the other way around.&lt;/li&gt;
&lt;li&gt;Because Drupal stores so much configuration and definitions of
    functionality in the database, it becomes incredibly difficult to
    version control the software.&lt;/li&gt;
&lt;li&gt;Drupal's default admin interface is riddled with UI and workflow
    problems (one of the primary focuses of the upcoming Drupal 7
    release).&lt;/li&gt;
&lt;li&gt;Django's default admin interface is designed to encourage quick
    and easy content management, and allows developers to quickly
    design custom admin interfaces and workflows.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p&gt;I don't mean to add fuel the Django versus Drupal fires, so let's just
rename this post to "Why you shouldn't introduce Drupal into a
Django-centric environment." I'm just looking to add some sanity to my
life.&lt;/p&gt;</content><category term="Technology"></category><category term="django"></category><category term="drupal"></category></entry><entry><title>Tomatohater.com is Mingus-ified!</title><link href="https://tomatohater.com/2010/05/11/tomatohatercom-is-mingus-ified/" rel="alternate"></link><published>2010-05-11T15:12:00-07:00</published><updated>2010-05-11T15:12:00-07:00</updated><author><name>Drew Engelson</name></author><id>tag:tomatohater.com,2010-05-11:/2010/05/11/tomatohatercom-is-mingus-ified/</id><summary type="html">&lt;p&gt;I just finished migrating this blog from vanilla django-basic-blog
to django-mingus.&lt;/p&gt;</summary><content type="html">&lt;p&gt;While django-basic-blog is perfectly sufficient to power this site, I
was interested in testing how &lt;a href="http://blog.montylounge.com/2010/02/7/mingus-09-released/"&gt;Monty
Lounge&lt;/a&gt;
extended it. You see, django-mingus isn't really an application itself;
there's really not much code involved. It is more like a recipe for
combining a number of existing reusable Django applications that make up
a solid blogging platform. The article &lt;a href="http://blog.montylounge.com/2009/sep/24/apps-that-power-django-mingus/"&gt;The apps that power
Django-Mingus&lt;/a&gt;
describes the 28 apps that have been nicely imaplemented into a single
project.&lt;/p&gt;
&lt;p&gt;My experience setting my Django-mingus blog was ridiculously simple. My
only issues were related to the pip install script...&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;BitBucket was down tonight...&lt;/li&gt;
&lt;li&gt;One updated dependency had had trouble finding the version specified
    ...&lt;/li&gt;
&lt;li&gt;One dependency was broken&lt;/li&gt;
&lt;li&gt;I had to change it to use git+http:// since my web host blocked
    traffic on the &lt;git://&gt; port&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Other than that, I didn't have to do much customization. I treated my
site design just like a new Mingus theme and attempted to make all my
changes in CSS. I did have to rearrange some of the HTML to support my
layout. I then did a bunch of clean-up in the Django templates to get
consistent page titles and page themes.&lt;/p&gt;
&lt;p&gt;I'm only using a few of the apps included with Mingus... I'll be
digging into the rest to see whether I'm missing out.&lt;/p&gt;</content><category term="Technology"></category><category term="blogware"></category><category term="django django-mingus"></category><category term="python"></category></entry><entry><title>Headed to RightScale User Meetup 2010</title><link href="https://tomatohater.com/2010/04/20/headed-to-rightscale-user-meetup-2010/" rel="alternate"></link><published>2010-04-20T15:09:00-07:00</published><updated>2010-04-20T15:09:00-07:00</updated><author><name>Drew Engelson</name></author><id>tag:tomatohater.com,2010-04-20:/2010/04/20/headed-to-rightscale-user-meetup-2010/</id><summary type="html">&lt;p&gt;I'm headed to NYC today for the Cloud Computing Expo and the
RightScale User Meetup 2010.&lt;/p&gt;</summary><content type="html">&lt;p&gt;Tomorrow I'll be presenting my experiences using
&lt;a href="http://www.rightscale.com"&gt;RightScale&lt;/a&gt; and &lt;a href="http://aws.amazon.com/"&gt;Amazon Web
Services&lt;/a&gt; to drastically change the way
&lt;a href="http://www.pbs.org/"&gt;PBS&lt;/a&gt; web applications are designed and hosted.&lt;/p&gt;
&lt;p&gt;Our foray into cloud computing is really still experimental, but it has
already proven to be transformative on a number of fronts... we are now
able to provide much more access and control to developers, while
supporting a wider variety of technologies, all the while achieving
higher availability. A win-win-win situation if you ask me.&lt;/p&gt;</content><category term="Technology"></category><category term="amazon-web-services"></category><category term="cloud"></category><category term="rightscale"></category></entry><entry><title>Jumping into Git</title><link href="https://tomatohater.com/2010/04/14/jumping-into-git/" rel="alternate"></link><published>2010-04-14T15:08:00-07:00</published><updated>2010-04-14T15:08:00-07:00</updated><author><name>Drew Engelson</name></author><id>tag:tomatohater.com,2010-04-14:/2010/04/14/jumping-into-git/</id><summary type="html">&lt;p&gt;The PBS development team is making the leap from Subversion to Git.&lt;/p&gt;</summary><content type="html">&lt;p&gt;So far, it has been relatively painless... We've imported a few SVN
repositories, maintained the full history, and development continues.
But we're still using Git as though it were Subversion.&lt;/p&gt;
&lt;p&gt;To make the most of Git will involve rethinking our development and
release practices. So far, Vincent Driessen's &lt;a href="http://nvie.com/git-model"&gt;A successful Git
branching model&lt;/a&gt; seems quite logical and is
the likely candidate for adoption. Let's see where that takes us.
Thanks, Vincent!&lt;/p&gt;</content><category term="Technology"></category><category term="git"></category><category term="pbs"></category><category term="version-control"></category></entry><entry><title>Python dict from two lists</title><link href="https://tomatohater.com/2009/04/05/python-dict-from-two-lists/" rel="alternate"></link><published>2009-04-05T15:01:00-07:00</published><updated>2009-04-05T15:01:00-07:00</updated><author><name>Drew Engelson</name></author><id>tag:tomatohater.com,2009-04-05:/2009/04/05/python-dict-from-two-lists/</id><summary type="html">&lt;p&gt;Beautiful Python one liner that creates a dictionary from two lists,
using one list as the dictionary keys and the other as the
dictionary values.&lt;/p&gt;</summary><content type="html">&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;key_list&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;x&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;y&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;z&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;value_list&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;combined_dict&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;zip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key_list&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value_list&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt; &lt;span class="n"&gt;combined_dict&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;x&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;y&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;z&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</content><category term="Technology"></category><category term="one-liner"></category><category term="python"></category></entry><entry><title>Deploying with Fabric and Subversion</title><link href="https://tomatohater.com/2009/02/03/deploying-with-fabric-and-subversion/" rel="alternate"></link><published>2009-02-03T14:58:00-08:00</published><updated>2009-02-03T14:58:00-08:00</updated><author><name>Drew Engelson</name></author><id>tag:tomatohater.com,2009-02-03:/2009/02/03/deploying-with-fabric-and-subversion/</id><summary type="html">&lt;p&gt;Deploying sites with Fabric is a cinch.&lt;/p&gt;</summary><content type="html">&lt;p&gt;Here's a sample fabfile for deploying to production and staging systems
directly from Subversion.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;production&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fab_hosts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;server1.example.com&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;server2.example.com&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;app_path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;/path/to/app/&amp;#39;&lt;/span&gt;

 &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;staging&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fab_hosts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;staging.example.com&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;app_path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;/path/to/app/&amp;#39;&lt;/span&gt;

 &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;svn_update&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="s2"&gt;&amp;quot;Updates the repository.&amp;quot;&lt;/span&gt;
    &lt;span class="n"&gt;sudo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;cd $(app_path); svn up&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

 &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;reboot&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="s2"&gt;&amp;quot;Reboot Apache2 server.&amp;quot;&lt;/span&gt;
    &lt;span class="n"&gt;sudo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/usr/sbin/apachectl restart&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

 &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;fab_hosts&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;provided_by&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;production&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="n"&gt;invoke&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;svn_update&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

 &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;deploy&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;fab_hosts&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;provided_by&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;production&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="n"&gt;invoke&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;svn_update&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;invoke&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;reboot&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Now, deploying is easy. After committing my changes to Subversion, I
simply instantiate:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="err"&gt;$&lt;/span&gt; &lt;span class="n"&gt;fab&lt;/span&gt; &lt;span class="n"&gt;production&lt;/span&gt; &lt;span class="n"&gt;deploy&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This is equivalent to:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="err"&gt;$&lt;/span&gt; &lt;span class="n"&gt;fab&lt;/span&gt; &lt;span class="n"&gt;production&lt;/span&gt; &lt;span class="n"&gt;update&lt;/span&gt; &lt;span class="n"&gt;reboot&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</content><category term="Technology"></category><category term="deployment"></category><category term="fabric"></category><category term="python"></category></entry><entry><title>Implementing Gravatar in Django</title><link href="https://tomatohater.com/2008/08/16/implementing-gravatar-in-django/" rel="alternate"></link><published>2008-08-16T14:55:00-07:00</published><updated>2008-08-16T14:55:00-07:00</updated><author><name>Drew Engelson</name></author><id>tag:tomatohater.com,2008-08-16:/2008/08/16/implementing-gravatar-in-django/</id><summary type="html">&lt;p&gt;A simple Django template tag to add Gravatar support to your site.&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;em&gt;Update (January 31, 2011) I just released
&lt;a href="/django-simplegravatar/"&gt;django-simplegravatar&lt;/a&gt;, a pluggable Django template
tag based heavily on this post.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;This blog is written in &lt;a href="http://www.python.org/"&gt;Python&lt;/a&gt; on
&lt;a href="http://www.djangoproject.com/"&gt;Django&lt;/a&gt;* and utilizes
&lt;a href="http://playgroundblues.com/posts/2007/dec/2/django-basic-apps/"&gt;basic.remarks&lt;/a&gt;
instead of
&lt;a href="http://code.djangoproject.com/wiki/UsingFreeComment"&gt;FreeComment&lt;/a&gt; for
the comments engine. To add some personalization to the comments, I
thought &lt;a href="http://en.wikipedia.org/wiki/Avatar_(computing)"&gt;avatars&lt;/a&gt; would
be a nice touch and decided to implement
&lt;a href="http://www.gravatar.com/"&gt;Gravatar&lt;/a&gt;, a fantastic system for centrally
managing one's avatar across multiple sites. Gravatar basically
associates an image with an e-mail address. Here's mine:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Drew Engelson's
Gravatar" src="http://www.gravatar.com/avatar.php?gravatar_id=58fbed519eea25fe2ed89fa3e3b21dfb"&gt;&lt;/p&gt;
&lt;p&gt;The general idea is to recognize a commenter's e-mail address and
automatically display their Gravatar (if they have one). Many web
applications have &lt;a href="http://en.gravatar.com/site/implement/"&gt;Gravatar
plugins&lt;/a&gt; such as Wordpress,
Joomla, MovableType, etc. I came across a Django project called
&lt;a href="http://code.google.com/p/django-gravatar/"&gt;django-gravatar&lt;/a&gt; which
attempts to provide this functionality, but since it relies on a User
object, commenters would have to create a User account. In general, most
commenters are not authenticated and would not have a User object in
Django. Clearly, django-gravatar is not for us. No matter...
implementing Gravatar couldn't be more simple.&lt;/p&gt;
&lt;p&gt;All that is required to display a user's Gravatar is their e-mail
address. The URL to the Gravatar image simply takes a hashed e-mail
address to identify the user, and a few other optional parameters to
control the size and what to do if there us no Gravatar associated with
their e-mail address. Here's how to create a custom template tag to
make this easy.&lt;/p&gt;
&lt;p&gt;First, we need to create the template tag Python code. In a file called
&lt;em&gt;myproject/templatetags/mytags.py&lt;/em&gt;, the following logic creates a
flexible custom tag.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;urllib&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nn"&gt;hashlib&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;template&lt;/span&gt;

&lt;span class="n"&gt;register&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;template&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Library&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="nd"&gt;@register&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;inclusion_tag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;templatetags/gravatar.html&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;show_gravatar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;48&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;default&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;http://www.mysite.com/media/images/no-avatar.gif&amp;quot;&lt;/span&gt;
    &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;http://www.gravatar.com/avatar.php?&amp;quot;&lt;/span&gt;
    &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;urllib&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;urlencode&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="s1"&gt;&amp;#39;gravatar_id&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;hashlib&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;md5&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;hexdigest&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="s1"&gt;&amp;#39;default&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;&amp;#39;size&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;gravatar&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;url&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;size&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Notice that the e-mail address gets md5 encoded, and that the whole
querystring is url encoded. Passing in the &lt;em&gt;size&lt;/em&gt; parameter enables this
tag to be used for Gravatars of varying sizes. The &lt;em&gt;default&lt;/em&gt; URL
specifies what to display if the commenter does not have a Gravatar.&lt;/p&gt;
&lt;p&gt;Next, we need to create the template that defines the HTML output for
the Gravatar image. In my case,
&lt;em&gt;myproject/templates/templatetags/gravatar.html&lt;/em&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;gravatar&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;img&lt;/span&gt; &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;{{ gravatar.url }}&amp;quot;&lt;/span&gt;
        &lt;span class="na"&gt;width&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;{{ gravatar.size }}&amp;quot;&lt;/span&gt;
        &lt;span class="na"&gt;height&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;{{ gravatar.size }}&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;border&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;0&amp;quot;&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Got it? Pretty simple, huh?&lt;/p&gt;
&lt;p&gt;Finally, we need to insert this custom tag into your template where the
Gravatar should display. In my case, this is the post_detail.html
template that controls my individual post page and comments. Here's the
code snippit:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;{% load mytags %}
{% if remark_list %}
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;comments&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;comments&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;h3&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;{{ remark_list|length }} comment{{ remark_list|pluralize }}&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;h3&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;

        {% for remark in remark_list %}
            &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;comment&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
                {% show_gravatar remark.person_email 48 %}
                &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;comment_header&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
                    {{ remark.person_name }} says...
                &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
                &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;comment_body&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
                    {{ remark.remark|urlizetrunc:&amp;quot;60&amp;quot; }}
                &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
                &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;comment_footer&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
                    Posted {{ remark.submit_date|date:&amp;quot;F j, Y, P&amp;quot; }}
                &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        {% endfor %}
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
{% endif %}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;There are two significant pieces of this code:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;"{% load mytags %}" makes the custom template available to this
    template. Remember we called the file &lt;em&gt;mytags.py&lt;/em&gt;?&lt;/li&gt;
&lt;li&gt;"{% show_gravatar remark.person_email 48 %}" instantiates the tag
    and passes the commenter's e-mail address and the desired Gravatar
    image size.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;You can use CSS to style this up to your heart's desire. Keep in mind
that you will always get a result back from Gravatar even if the
commenter does not have a Gravatar. We specified a default image for
these cases. If no &lt;em&gt;default&lt;/em&gt; was specified, Gravatar will return its own
default image (a blue Gravatar logo).&lt;/p&gt;
&lt;p&gt;&lt;img alt="default
Gravatar" src="http://www.gravatar.com/avatar.php?gravatar_id=doh&amp;amp;size=48"&gt;&lt;/p&gt;
&lt;p&gt;We could extend this to support the remaining Gravatar options, such as
ratings, but I'll leave that up to you.&lt;/p&gt;
&lt;p&gt;* In case you missed it, this is sarcastic reference to &lt;a href="http://www.rubyonrails.org/"&gt;Ruby on
Rails&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;UPDATE (5/11/2010): The first paragraph of this article no longer
accurately describes this blog's software architecture. It no longer
uses basic.remarks, but was converted to &lt;a href="http://blog.montylounge.com/2009/07/1/welcome/"&gt;Django
Mingus&lt;/a&gt; and uses
&lt;a href="http://disqus.com/"&gt;Disqus&lt;/a&gt; for comments (which has built-in Gravatar
support).&lt;/em&gt;&lt;/p&gt;</content><category term="Technology"></category><category term="avatar"></category><category term="django"></category><category term="django-simplegravatar"></category><category term="gravatar"></category><category term="python"></category></entry><entry><title>Multi-blog MovableType... a bad idea?</title><link href="https://tomatohater.com/2008/07/31/multi-blog-movabletype-a-bad-idea/" rel="alternate"></link><published>2008-07-31T01:16:00-07:00</published><updated>2008-07-31T01:16:00-07:00</updated><author><name>Drew Engelson</name></author><id>tag:tomatohater.com,2008-07-31:/2008/07/31/multi-blog-movabletype-a-bad-idea/</id><summary type="html">&lt;p&gt;Trials and tribulations of multi-blog MovableType (3.x).&lt;/p&gt;</summary><content type="html">&lt;p&gt;We have been running a single install of MovableType 3.21 to run
approximately 33 different blogs. This setup has been running for a few
years now, and though it has not been without it's difficulties,
overall the system was stable. However, for the past few months, some of
the blogs have been experiencing increasingly horrendous rebuild times.
Additionally, since comment posting triggers a rebuild, the system
times-out before the commenter sees any response to their browser
resulting in a user experience similar to sticking one's hand in a meat
grinder.&lt;/p&gt;
&lt;p&gt;I'm sure this is merely a result of numerous factors compounding one
another:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Old version of MT&lt;/li&gt;
&lt;li&gt;Very large number of posts (?)&lt;/li&gt;
&lt;li&gt;Inefficiently built templates&lt;/li&gt;
&lt;li&gt;Too many (and poorly written) plugins installed&lt;/li&gt;
&lt;li&gt;Underpowered server&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;However, it seems to me that the MT's architecture also deserves its
share of the blame. Many of the installed plugins are only used by one
(or few) blog, yet the 32 other blogs suffer as a result since they all
must process the plugin, even if there is nothing to do. I don't think
this is any different in MT 4.1 (or 4.2). Anyone?&lt;/p&gt;
&lt;p&gt;In the end, we decided to split the blogs off onto their own MT installs
and reinstall only the plugins required for each blog (no simple task I
must say). So now we've created for ourselves a maintenance nightmare,
but hey, at least MT-CustomFields won't break some of my blogs anymore.&lt;/p&gt;</content><category term="Technology"></category><category term="blogware"></category><category term="movabletype"></category></entry></feed>