Downloading PDF Documentation for ReadTheDocs Projects

Every Python developer is familiar with Read The Docs‘ beautiful HTML documentation for numerous open source projects. However, few developers know that Read the Docs hosts PDF versions of every project’s documentation.

Here, for example, is the url to Django-Tastypie’s PDF docs:


You can replace django-tastypie with the slug for any Read the Docs project.


Politician Market – Post Launch Writeup on Conversions, AppEngine, and Launching

On Tuesday, I released Politician Market, a satirical, fake, marketplace for buying US politician votes. I brought it online, posted to Hacker News, and went to take a shower. 15 minutes later, I’m wearing a towel when I notice that Politician Market is #1 on HN and the server is melting. 12 requests per second. Gosh. I spent the next hour coding in underpants only. My roommate walked in with family members in the middle. He just pointed, said something with the word “roommate”, and walked out.

I wanted Politician Market to be entirely static site, so I used JotForm for signups. There were so many sign-ups that I crashed JotForm’s servers after only 10 minutes and had to move the sign-up form to Google Docs.

Signups and Conversions

Conversion tip: If you want to optimize sign-ups, don’t use  an external service. Sign-ups stopped almost entirely after moving to Google Docs. (I received only 40 signups after the move.) If you want to gain feedback, do place a feedback form on the homepage and you’ll get plenty of feedback.

12,000 uniques and 4 hours later, Politician Market mysteriously disappeared from the homepage and traffic started to subside. Some time before then, I had to disable the contact form entirely because I was about to overrun my JotForm quota. Despite disabling the form, people kept submitting it, often blank. (A browser bug?) At that point, I changed the form’s action to “#” and added an overlay (see below), but people kept submitting the damn form. My brother told me that he wrote me a long message in the contact form, because he was convinced that it really did get sent after all. What must you do for people to believe you?

Another conversion tip: Adding social sharing buttons seems to matter. There were 12,000 hits while we were on HN for four hours. After pg killed the submission, the long tail brought (and is still bringing) another 2k. If you add sharing buttons, make sure to include Google Plus. The break down was 259 shares on Twitter, 40 on Google Plus, and a bunch on Facebook. (For an unknown reason, Facebook wont show me the stats anymore.)

I didn’t have time to A/B test, but conversion rates seemed to rocket when I placed annoying sharing links so far up that they pushed content below the fold. I hated that, so I soon reverted the changes. (Startup idea: I wish I could run automatic A/B tests by including a javascript file that would hash the page, check with a 3rd party server to see if it changed, and provide conversion data when I’m cowboy coding. Create that and I will pay a one time $10 fee for the service.)

For absolutely no reason, I asked for phone numbers in the signup form. About 30% of all sign-ups included them.

Avoid AppEngine Like the Plague

I originally wanted to host on S3, but it wouldn’t accept my credit card so I used AppEngine and static-app-engine-hoster. Don’t do that. There is no caching and you lose all the benefits of a static site. I wasn’t expecting so many hits, so I didn’t think this through.

More on AppEngine: Deployment is easy, but payment sucks. AppEngine makes you pay per-week, so if you want to use $10 a day, you have to deposite at least $70. (Slimeballs.) AppEngine has absolutely no support for paying customers. I sent two emails about urgent issues and never heard back. Avoid AppEngine like the plague.


I tried publicizing Politician Market on Reddit, Slashdot, and Digg. It almost took off on Reddit, but Slashdot ignored it despite votes on the Firehose submission and Digg was a laughable waste of time. (What they say about Digg is correct: If you aren’t part of the oligarchy you might as well submit the link to /dev/null.) I emailed a few tech and political bloggers. Most of them ignored me, but some had incredibly kind words and published a link. Do have the chutzpa to email bloggers.

This is the second launch I have done. Third time ice cream. Does anyone know where that weird Israeli saying comes from?



I decided to learn Ruby, so I present my first experiment in a new language: Termbrot!

Termbrot iterates over points in the complex plane, animating the image as it discards points from the Mandelbrot set. Get it on GitHub.


Use of void in C prototypes

A giant died this morning. My condolences go to Dennis Ritchie’s family and friends.

In memory of Dennis, here is a short history lesson about C prototypes. Have you ever wondered why the following is legal?

#include <stdio.h>;

void foo () {
    printf("Goodbye, World!\n");

int main (int argc, char **argv) {
    foo(1); /* Call foo with a non-existent parameter */
    return 0;

$ gcc test.c -Wall -Werror
$ ./a.out
Goodbye, World!

This is legal for historical reasons. In pre-ANSI C function prototypes didn’t include parameters. C89 introduced parameters in prototypes, but it continued to recognize the old syntax for backwards compatibility.

If you want to declare a function foo that really takes no parameters, use void:

void foo (void);

Installing lxml on Leopard

I’m surely the last developer using Leopard, but Snow Leopard couldn’t compete with Ubuntu and Lion has more warts than the Wicked Witch of the West.

Anyway, you can’t just install lxml with pip (the preferred way of installing Python libraries) because lxml depends on a recent version of libxml2. If you try, gcc will stop when it encounters missing headers added in later versions of libxml2:

libxml/schematron.h: No such file or directory blah blah blah

Instead, use the fabulous Homebrew to install a newer version of libxml2 and then run pip with that version.

brew install libxml2
pip install lxml --install-option="--with-xml2-config=/usr/local/Cellar/libxml2/2.7.8/bin/xml2-config"
Pages ... 1 2 3