« | » Main « | »

An emergency at Tabblo

Tuesday 26 September 2006

What happens at a photo-sharing web startup when the sprinkler system suddenly goes off, flooding the office with thousands of gallons of water? They take pictures and make a tabblo about it to share the story: An Emergency at Tabblo.

An Emergency at Tabblo

It really is quite a mess in the offices. It will take at least a few days to correct things. As Antonio put it: "These are the moments that define a startup".

WebInno 8

Tuesday 26 September 2006

I attended WebInno 8 last night in Cambridge. This was my third WebInno, so it's becoming a regular crowd of people now.

There were three companies doing formal presentations:

  • FatCalico, doing something with podcasts on phones, involving forwarding podcasts to friends, dynamically building podcast shows, and inserting ads. I use my phone exclusively for talking to people, so my eyes kind of glaze over during mobile demonstrations. Erik Schwartz was doing a good job working through the difficulties of how to show a phone screen to the crowd, and demo'ing in general. At one point, he had the typical demo problems: "um, that didn't work, let me try it again". He seemed to have a real business background, since he was throwing around terms like CPC and CPM, and "call to action" (in the marketing sense).
  • Traineo came next, but Alasdair McLean-Foreman's laptop blue-screened with a parity error when he plugged it into the projector. I don't know that I've ever seen that happen. The crowd couldn't help but chuckle about the demo gods on that one, though I know they all felt for him.
  • The Echo Nest gave a very cool demo. Tristan Jehan and Brian Whitman are building an automated music intelligence system. "Automatically knows about all music in the world, all the time." They have software that listens to music and analyzes if for structure, pitch, rhythm, instrumentation, and so on. Their search engine can answer queries like "madonna", but also, "something quiet and romantic". They do language processing on pages that link to songs, so they can associate the adjectives and nouns with the songs. They have a lot of very cool technology, and are building an open API to access it. They showed a video of a friend who made a PSP dj application using the API.
  • Traineo came back with a rebooted laptop, and showed their site, which helps people set and meet weight loss goals. It seemed well-designed and would probably help people lose weight. They're very proud of their factoids ("Traineo female members from New Zealand consumed 1503 calories on average today"), but they were a little too prominently displayed for my taste, and quickly grew tiresome. When asked about how they would make money, Alasdair replied, "We signed up 10,000 users the first day, and the weight loss industry is a $10B/year market", which wasn't really answering the question, though we could all see where it was headed: ads.

SQL trivia

Saturday 23 September 2006

I've been digging deeper into SQL details, especially for MySQL, and have found a few tidbits:

1. In a GROUP BY clause, you can use integers to indicate the column number to group by. I knew this was possible with ORDER BY, so it made sense that it would work for GROUP BY, but it never occurred to me to try it. These pairs of statements are equivalent:

select a, b from foo order by a, b;
select a, b from foo order by 1, 2;

select a, b, count(*) from foo group by a, b;
select a, b, count(*) from foo group by 1, 2;

2. MySQL provides a WITH ROLLUP modifier for grouped selects. This inserts sub-total and total rows in the appropriate places:

mysql> select color, count(*) from foo group by 1;
+-------+----------+
| color | count(*) |
+-------+----------+
| blue  |        6 |
| green |        6 |
| red   |        4 |
+-------+----------+
3 rows in set (0.00 sec)

mysql> select color, count(*) from foo group by 1 with rollup;
+-------+----------+
| color | count(*) |
+-------+----------+
| blue  |        6 |
| green |        6 |
| red   |        4 |
| NULL  |       16 |
+-------+----------+
4 rows in set (0.00 sec)

3. At least according to these thorough and authoritative-looking SQL grammars, the LIMIT clause is not a part of any SQL standard!

Do you digg Tabblo?

Wednesday 20 September 2006

A small favor: please visit Digg and vote up the link to Tabblo. (If you aren't familiar with Digg: When you get to that page, there's a badge that says "20 diggs", with a "digg it" link underneath. Click it!) Thanks!

One of the difficulties in working for a startup is getting the word out about the product you've built. I think Tabblo is great, and I want as many people as possible to see it. But there are a lot of startups out there, and all of them claim to be revolutionary, etc, and all are clamoring for attention. Every little bit helps.

The dynamics of Digg links are the subject of much speculation, and their machinery is probably designed to resist explicit attempts to move links up the page, but it's got to be better to have the votes than not have them, right?

BTW: Tabblo is also running a Fall Foliage Contest.

Naming planets

Saturday 16 September 2006

Planets have been in the news a lot lately, what with Pluto being demoted and all. Yesterday brought the announcement of a puffy planet larger than Jupiter. This is fascinating stuff, but here's what I don't get: how do they choose the names for these distant planets? Here in our neighborhood, scientists choose mythological names that might mean something. So the troublemaker out near Pluto was named Eris.

But the new puffy planet has been named HAT-P-1, while another planet mentioned alongside it is HD 209458b. I wondered where these names came from. The Space.com article finally makes clear that HAT-P-1 was found by a network of telescopes named HAT, so I guess it's the first planet found with that network. But that means that a future HAT-P-2 could be on the other side of the galaxy, hundreds of light-years from HAT-P-1. And poor HD 209458b: is there an HD 209458a? Why not take the time to give them more interesting names?

Escher-like Google maps

Friday 15 September 2006

I used Google maps yesterday to pinpoint a location in downtown Boston last night, and was presented with an Escher-like perspective. All of the buildings on the east side of Berkeley street seem to lean to the south, and all the ones on the west lean to the north. The picture was clearly stitched together from two shots, but the stitch runs seamlessly down the street, rather than along a simple geographic line. Someone is doing some impressive hand-work to make these photos seamless.

TechCrunch Tabblo review review

Tuesday 12 September 2006

Marshall Kirkpatrick reviewed Tabblo on TechCrunch today. I'm of two minds about the review.

On the one hand, TechCrunch is a widely-read blog (as of this writing, it is sixth on Technorati's list of top blogs). It will surely bring Tabblo attention, and from an influential set of people.

And the review is very positive. Despite some qualifiers, the tone is upbeat and enthusiastic, so I am thankful for that.

But there are a number of jarring notes, beginning with the very first word (in the title): "Wiki your photo posters with Tabblo". When did we verb "wiki"? I thought it was a noun. And while I think association with the "wiki ethos" (as Marshall puts it) is a good thing, I'm not sure we have that association. Maybe we do, and I just need to get used to the cognitive leap Marhall has made.

Marshall casts our goals a bit narrowly. He's drawn us as purely concerned with printed products. It's true we have put a lot of energy into making great-looking printed products, and we have based our business model on revenue from those products. Our attention to print is one of the things that sets us apart from other photo sites.

But we are also building a great site for sharing stories through photos, even if they are only online. A spirited community is taking shape on Tabblo, as people share photos and tabblos, and get to know each other from their work. So while printing is a big part of the Tabblo ecosystem, it is not the whole story.

Just on a technical level, I was disappointed by this:

The template design function in Tabblo is quite usable. It's in dHTML, not ajax or flash...

Ajax must be one of the most over-hyped and mis-understood technologies around these days (this is the second time in two weeks that I've heard Ajax contrasted not with an opposite, but with a complement). And what's with the capitalization? Can't we get a modicum of editing on this post? I don't expect the writers at TechCrunch to be writing code, but is it too much to ask that they understand the fundamentals of the technologies they are touting or trashing?

I shouldn't look a gift horse in the mouth. The review is positive. Marshall mentions a number of our feature innovations, including variations (letting others do a Save As on your tabblo, and using it as a starting point for the own creative impulses), our template engine, and undo, to name just a few. The comments include the usual trolls, but we're getting good feedback there as well. The word about Tabblo is spreading.

Show image catalog Greasemonkey script

Sunday 10 September 2006

I'm a late-comer to the magic of Greasemonkey. It's a powerful Firefox add-in that lets you run bits of JavaScript on pages. There are a ton of Greasemonkey scripts available, many designed to fix or add features to particular sites.

Here's my first script: it provides a menu command to display a catalog of the images on a page. It can be very helpful for picking apart a page for debugging, for example. It's kind of basic. For example, if the same image is used more than once on the page, it will be listed more than once.

It's short:

// ==UserScript==
// @name          Image Catalog
// @namespace     http://nedbatchelder.com/
// @description   Shows a catalog of the images on a page.
// @include       http://*
// @include       https://*
// @include       file://*
// ==/UserScript==

GM_registerMenuCommand("Show image catalog", show_image_catalog);

function show_image_catalog() {
    var img_display = document.createElement("div");
    ihtml = "<div style='margin: 10px auto 0 auto; border-top: 2px dotted black; background-color: white;'>";

    var imgs = document.getElementsByTagName("img");
    for (var i = 0; i < imgs.length; i++) {
        img = imgs[i];
        ihtml += "<div style='margin: 5px 10px'>";
        ihtml += "<img src='" + img.src + "'/>";
        ihtml += "<br/>";
        ihtml += "src = <a href='" + img.src + "'>" + img.src + "</a>";
        ihtml += "<br/>";
        ihtml += "width = " + img.width + ", height = " + img.height;
        if (img.width != img.naturalWidth || img.height != img.naturalHeight) {
            ihtml += ", <span style='color:red; font-weight: bold'>";
            ihtml += "naturalWidth = " + img.naturalWidth;
            ihtml += ", naturalHeight = " + img.naturalHeight;
            ihtml += "</span>";
        }
        ihtml += "</div>";
    }
    ihtml += "</div>";
    img_display.innerHTML = ihtml;
    document.body.insertBefore(img_display, null);
}

Here's a direct link to the script: image_catalog.user.js. If you have Greasemonkey installed, clicking the link will install it.

Impoverished exceptions

Wednesday 6 September 2006

Yesterday's post about testing for exception details turned into a surprisingly lively discussion about what is the interesting detail in exceptions, and how to test for it.

My string comparison was quickly upped to substring comparison, and then to regex matching. Then it was pointed out that in different locales, the exception messages can be completely different.

As it happens, this came up again for me in a different context. One of the things test runners need to do is find test code nestled in among the application code under test. Here's sample code for doing this (from Russ Magee's new test framework for Django:

# Check to see if a separate 'tests' module exists parallel to the 
# models module
TEST_MODULE = 'tests'
try:
    app_path = app_module.__name__.split('.')[:-1]
    test_module = __import__('.'.join(app_path + [TEST_MODULE]), [], [], TEST_MODULE)
    suite.addTest(testLoader.loadTestsFromModule(test_module))
except ImportError:
    # No tests.py file for application
    pass

This works great: if there's a tests.py file, it will be imported and added to the test suite. If there isn't, it will silently move on. But what if you have a tests.py file, but it has an import error in it? I mis-type imports all the time, or move code between files and let the interpreter find the imports I need to bring along. If my tests.py file has an import error, it is suppressed, and the tests are found, and I don't get told about it.

I changed the code to this:

# Check to see if a separate 'tests' module exists parallel to the 
# models module
TEST_MODULE = 'tests'
try:
    app_path = app_module.__name__.split('.')[:-1]
    test_module = __import__('.'.join(app_path + [TEST_MODULE]), [], [], TEST_MODULE)
    suite.addTest(testLoader.loadTestsFromModule(test_module))
except ImportError, exc:
    # No tests.py file for application, or some other import error.
    if str(exc) != 'No module named %s' % TEST_MODULE:
        # It's something other than a missing tests module, probably a real
        # error, so show the user.
        import traceback
        traceback.print_exc()

Now if tests.py doesn't exist, it will still be silent, but any other error will be displayed to the user. Better: when my tests aren't found, I can see why. But there we are testing exception messages. If this code were run on a non-English installation, the logic would be all wrong. One more edge case: suppose deep in my tests.py, there's an import for another module named "tests"? If that import fails, the exception will still be swallowed.

This is one of the difficulties in error handling in general: when something goes wrong, how do you express it richly enough so that the caller can understand the problem well enough to do something about it?

Clearly, just knowing that an ImportError happened is not enough. Here we want to distinguish between two different causes of ImportError. The exception itself only has one piece of data on it: a message. Wouldn't it be better if it had some structured information as well. As Calvin Spealman pointed out on yesterday's post, it would be great if the details of the exception were available without trying to parse a human-readable message.

Why doesn't ImportError have the name of the module that couldn't be imported? Then we could do something like this:

...
except ImportError, exc:
    # No tests.py file for application, or some other import error.
    test_path = '.'.join(app_path + [TEST_MODULE])
    if exc.module_path != test_path:
        # It's something other than a missing tests module, probably a real
        # error, so show the user.
        import traceback
        traceback.print_exc()

Now we can do the test we want to do. We aren't beholden to any particular locale, and we can distinguish between different tests.py that couldn't be imported.

All sorts of Python exceptions could be extended this way. Calvin's original example was AttributeError having the object and attribute name that couldn't be found on it. The good news is that this could be added to Python at any time.

assertRaisesMsg

Tuesday 5 September 2006

I use the standard unittest module for testing, despite the community's general protestations about it not being pythonic. It does what I need, except for one thing. It has an assertRaises method that tests that a callable raises an exception of the expected class, but the message that comes with the exception is not checked. So I usually mix this method into my test case base class:

def assertRaisesMsg(self, excClass, msg, callableObj, *args, **kwargs):
    """ Just like unittest.TestCase.assertRaises,
        but checks that the message is right too.
    """
    try:
        callableObj(*args, **kwargs)
    except excClass, exc:
        excMsg = str(exc)
        if not msg:
            # No message provided: any message is fine.
            return
        elif excMsg == msg:
            # Message provided, and we got the right message: it passes.
            return
        else:
            # Message provided, and it didn't match: fail!
            raise self.failureException(
                "Right exception, wrong message: got '%s' expected '%s'" % 
                (excMsg, msg)
                )
    else:
        if hasattr(excClass, '__name__'):
            excName = excClass.__name__
        else:
            excName = str(excClass)
        raise self.failureException(
            "Expected to raise %s, didn't get an exception at all" % 
            excName
            )

I can call it (for example) like this:

self.assertRaisesMsg(MyException, "Exception message", my_function, (arg1, arg2))

This test line will call my_function(arg1, arg2) and succeed if it raises a MyException with the message "Exception message". It will fail in all other cases.

« | » Main « | »