Monthly Archive for April, 2007

Fun With Python BDD

I was testing my algorithm/data structures chops by implementing a Linked List from scratch using no reference material (not very difficult, I know), and I must say that using BDD was incredibly helpful. I’m calling it “BDD” instead of “TDD” just because the test class names describe a context and the method names in the test classes are behavior-oriented, inspired by the style found on the RSpec site.

I didn’t write the tests/specs up front like you’re “supposed” to. Instead, I thought about what I was trying to do, came up with an initial idea for the implementation, started writing code, and then added a test to make sure I was on the right track. This worked out pretty well. I also used the tests during refactoring. Writing the tests first might have worked out better, but I can’t say for sure.

Now, for some real fun, implement a linked list based on only the test code below.

class LinkedList(object):
    def __init__(self, *values):
        self._size = 0
        if values:
            for v in values:
                self.add(v)
        else:
            self.head = self.tail = None

    def is_empty(self):
        return len(self) == 0

    def add(self, val):
        node = Node(val)
        if self.is_empty():
            self.head = node
        else:
            self.tail.next = node
        self.tail = node
        self._size += 1

    def get(self, index, get_prev=False):
        if index < 0:
            index = self._size + index
        if index < 0 or index >= self.length:
            raise IndexError
        prev = None
        for i, node in enumerate(self):
            if i == index:
                if get_prev:
                    return node, prev
                else:
                    return node
            prev = node

    def remove(self, index):
        node, prev = self.get(index, get_prev=True)
        if node is self.head:
            self.head = node.next
        else:
            prev.next = node.next
        self._size -= 1
        return node

    def pop(self):
        return self.remove(self.length - 1)

    def __len__(self):
        return self._size
    length = property(__len__)
    size = property(__len__)

    def __iter__(self):
        curr = self.head
        while curr is not None:
            yield curr
            curr = curr.next
        raise StopIteration

    def __str__(self):
        return ', '.join([str(node.value) for node in self])

class Node(object):
    def __init__(self, val, next=None):
        self.value = val
        self.next = next
import unittest
class Test_A_New_Linked_List(unittest.TestCase):

    def test_given_no_values_should_be_empty(self):
        list_ = LinkedList()
        assert list_.is_empty()

    def test_given_no_values_should_have_size_0(self):
        list_ = LinkedList()
        assert list_.size == list_.length == 0

    def test_given_values_should_not_be_empty(self):
        list_ = LinkedList(1, 2, 3)
        assert not list_.is_empty()
        assert [node.value for node in list_] == [1, 2, 3]

    def test_given_4_values_should_have_size_4(self):
        list_ = LinkedList(1, '2', 3, '4th value')
        assert list_.size == 4

class Test_An_Empty_List(unittest.TestCase):

    def test_should_have_size_1_after_add(self):
        list_ = LinkedList()
        val = 15
        list_.add(val)
        assert list_.head.value == list_.tail.value == val
        assert len(list_) == list_.length == list_.size == 1

    def test_should_raise_an_index_error_on_get(self):
        list_ = LinkedList()
        self.assertRaises(IndexError, list_.get, 1)
        list_ = LinkedList(1, 2, 3)
        list_.remove(0); list_.remove(0); list_.remove(0)
        self.assertRaises(IndexError, list_.get, 0)

    def test_should_raise_an_index_error_on_remove(self):
        list_ = LinkedList()
        self.assertRaises(IndexError, list_.remove, 1)
        list_ = LinkedList(1, 2, 3)
        list_.remove(0); list_.remove(0); list_.remove(0)
        self.assertRaises(IndexError, list_.remove, 0)

    def test_should_raise_an_index_error_on_pop(self):
        list_ = LinkedList()
        self.assertRaises(IndexError, list_.pop)

class Test_A_Non_Empty_List(unittest.TestCase):

    def test_should_increase_its_size_by_1_on_add(self):
        list_ = LinkedList(1, 2, 4, 5)
        starting_size = list_.size
        list_.add(13)
        assert len(list_) == list_.length == list_.size == (starting_size + 1)

    def test_should_return_a_node_on_get(self):
        list_ = LinkedList(1, 2, '3rd value')
        node = list_.get(0)
        assert isinstance(node, Node)
        assert node.value == 1
        node = list_.get(1)
        assert isinstance(node, Node)
        assert node.value == 2
        node = list_.get(2)
        assert isinstance(node, Node)
        assert node.value == '3rd value'

    def test_should_decrease_its_size_by_1_on_remove(self):
        list_ = LinkedList(1, 2, 4, 5)
        starting_size = list_.size
        list_.remove(0)
        assert len(list_) == list_.length == list_.size == (starting_size - 1)

    def test_should_decrease_its_size_by_1_on_pop(self):
        list_ = LinkedList(1, 2, 4, 5)
        starting_size = list_.size
        list_.pop()
        assert len(list_) == list_.length == list_.size == (starting_size - 1)

Google Maps Encoded Polylines

Update 6/30/07: Fixed links to glineenc.py so that it’s actually accessible.
Update 7/15/08: Fixed links to glineenc.py again because of change to HTTPS on Trac site.

Here’s some Python code (complete with unit and doc tests) for converting a series of latitude/longitude points (i.e., a polyline) to the Base64 encoding that Google Maps understands. It’s particularly useful for long and/or complicated lines.

It’s based on the algorithm listed here and the JavaScript code here [page disappeared].

This site gives some more insight into it and has a pretty cool example of a fractal line here.

Here’s the code:

Previously, I had pasted the Python code right into this article, but I recently made a bunch of revisions and it was way too long. Here’s a link to the code instead:

glineenc.py on Trac

Please note that this code is still in somewhat of a rough state. I have plans to polish and package it up, but for now, I’m using it as is and it’s working quite well (you’ll have to be patient to click that link as it takes ~20-30 seconds to generate the route, even though the line drawing itself is almost instantaneous).

JavaScript that uses results from `glineenc` looks something like this (assuming you’ve returned some JSON, say, with `encoded_points` and `encoded_lines` keys):


map.addOverlay(new GPolyline.fromEncoded({
  color: "#0000ff",
  weight: 4,
  opacity: 0.8,
  points: result.encoded_points,
  levels: result.encoded_levels,
  zoomFactor: 32,
  numLevels: 4
}));

`points` is the encoded lat/long points. `levels` indicates which zoom levels each point should displayed at; there is one character per point. See the links above for a more complete explanation.

Restler, a RESTful Base Controller for Pylons

“Restler is a base controller for Pylons projects that provides a set of default RESTful actions that can be overridden as needed. It also handles database connectivity as long as a few simple rules are followed.

It adds a bit of ‘convention-over-configuration’ to Pylons and takes some inspiration from Rails’ scaffold_resource generator…”

Restler’s aim is twofold: 1) make it easier to get started with Pylons and 2) encourage a RESTful project architecture.

It attempts to remove some of the pain of database setup in a Pylons project by providing a default configuration. All users need to do is specify their connection settings with one line in a config file.

The project is hosted at Google Code. There is a “Quick Start” guide there on the project home page (which also serves as a mini introduction to getting started with Pylons).

All feedback is welcome; please feel free to make use of the issue tracker.

PS If anyone happened to come across Restler on the Cheeseshop previously, some of the wonkiness has been removed (e.g., use of execfile to “include” restler) and most of the actions have been filled out such that they actually do (something closer to) the right thing.

PPS Sorry about the comment spam earlier on Planet Python. I’m not really sure why comments are coming through, and I don’t see any settings in Mephisto that would allow me to change that. I disabled comments for the offending post and emailed the Planet Python moderator to see if there’s anything that can be changed on that end.