true false maybe

tom longson’s blog on software, design, and user experience
May 3, 2009

Be Cool

Andrew says to his kids "What's the rule?"

becool

April 8, 2009

Tagging with SQLAlchemy

I wanted to use tags with SQLAlchemy, so I looked up previous examples and found Wayne's post on how he did it. I adapted his code into a single file example so you can see better how it works. For any given page, there can be any number of tags .appended to it. For any given tag, you can .append it to any number of pages.

For example:

page = Page(u"Example Page")
page.append(Tag(u"examples"))
from sqlalchemy import *
from sqlalchemy.orm import *
engine = create_engine('sqlite://')
metadata = MetaData(engine)
 
#engine.echo =True 
 
page_table = Table("page", metadata,
    Column("id", Integer, Sequence('page_seq_id', optional=True), primary_key=True),
    Column("name", Unicode(100), nullable=False),
)
 
tag_table = Table("tag", metadata,
    Column("id", Integer, Sequence('taq_seq_id', optional=True), primary_key=True),
    Column("name", Unicode(50), nullable=False, unique=True),
)
 
pagetag_table = Table("pagetag", metadata,
    Column("id", Integer, Sequence('pagetag_seq_id', optional=True), primary_key=True),
    Column("pageid", Integer, ForeignKey('page.id')),
    Column("tagid", Integer, ForeignKey('tag.id')),
)
 
class Tag(object):
    def __init__(self, name):
        self.name = name
 
    def __repr__(self):
        return "Tag(\"%s\")" % self.name
 
class Page(object):
    def __init__(self, name):
        self.name = name
 
    def __repr__(self):
        return "Page(\"%s\")" % self.name
 
mapper(Tag, tag_table)
mapper(Page, page_table, properties = {
    'tags':relation(Tag, secondary=pagetag_table, cascade="all"),
    #    'tags':relation(Tag, secondary=pagetag_table, cascade="all,delete-orphans"),
})
 
metadata.create_all()
 
sess = create_session()
 
page = Page(u"Tags with SQLAlchemy Example")
page2 = Page(u"Hot New Video Game Consists Solely Of Shooting People Point-Blank In The Face")
page3 = Page(u"Congressman's War Hero Son Would Have Wanted Highway Bill Passed")
 
tag = Tag(u"examples")
tag2 = Tag(u"onion")
 
page.tags.append(tag)
page2.tags.append(tag2)
page3.tags.append(tag2)
 
sess.add(page)
sess.add(page2)
sess.add(page3)
sess.flush()
 
tag_q = sess.query(Tag)
tags = tag_q.all()
print "Number of tags:", len(tags)
 
# filter pages by tag(s)
page_q = sess.query(Page)
pages = page_q.join('tags').filter_by(name=u"tag").all()
 
print
print "First Page"
print page_q.first()
print page_q.first().tags
 
print
print "Second Page"
print page_q.all()[1]
print page_q.all()[1].tags
 
print
print "Third Page"
print page_q.all()[2]
print page_q.all()[2].tags
 
# delete-orphans does the work for us here...
#sess.delete(pages[0]
#sess.flush()
 
print
print "All tags"
tags = tag_q.all()
print tags, "Count:", len(tags)
 
print
print "Tag cloud anyone?"
# see the source code linked below for a properly weighted tag cloud.
tag_q = sess.query(func.count("*").label(u"tagcount"), Tag)
tag_r = tag_q.filter(Tag.id==pagetag_table.c.tagid).group_by(Tag.id).all()
#print tag_q
print tag_r
 
# what about pages with related tags?
page_q = sess.query(Page)
 
taglist = [u"tag1", u"tag2"]
tagcount = len(taglist)
page_q.join(Page.tags).filter(Tag.name.in_(taglist)).\
group_by(Page.id).having(func.count(Page.id) == tagcount).all()

I know tag clouds are passe, but I still think from an information architecture perspective, tags still better than categories.

February 24, 2009

Giving up Reddit for Lent

I'm not religious but I like the idea of Lent because it fits into the idea of changing our habits, which is hard to do and potentially has dramatic long standing effects on how we live.

I for one love reading Reddit and sometimes Digg, but I find it to be somewhat a sinkhole. Sure it's funny to read about how someone destroyed their finger with magnets or see a cute picture of a coyote on the BART, but from a learning perspective, I'd be better off spending my time on hacker news.

So to that end, I'm modifying my host file to give up reddit for lent. Who knows, maybe instead of removing the entry after lent, I'll add more instead.

Here's how you can do it too:
Windows | OSX | Linux

December 16, 2008

RIP Phild0g

I'm bummed out. My old friend, Phil passed away last monday and I missed the get together this last weekend because I've been off the social grid these past months. The above photo is probably the best picture I took in high school, and was used to create a flyer for the Rollcall parties I helped (in a small part) to throw with Phild0g. This picture was taken in the desert, a home away from home for all of us.

Here's another design I did for Burning Man Earth's poster. Again, it's rough (can you spot the misspelling?). The idea with this poster is to draw the viewer in with tiny text, and as they get closer, they should see detail in the aerial photography. Having looked at this and the other poster for a while now, I think I prefer the first one. I don't think I'm going to do any more work on this unless one of these concepts goes forward.

Click to see the high resolution version of the photo.

My camp, Burning Man Earth is trying to design a poster to help us fundraise for 2009, and this is a concept I came up with yesterday. It's a bit rough, and I think I'm giving up on it because it's a bit too bold, and Rod Garrett (Burning Man's City Designer) has suggested that it draw the viewer in, and be as simple as possible.

In any case, I am proud of what I did in just a few hours.

If you have any feedback, please comment. I would love to know what you think.

So you want to find out why your Pylons app is running slowly? Well most likely it has to do with your SQL queries, and the best way to see what's going on and how long each request is taking is to install Dozer (by benbangert of Pylons), and load it up with a TimerProxy (by zzzeek of SQLAlchemy).

Sound like fun? Well, here's how to do it.

Install Dozer:

sudo easy_install -U http://www.bitbucket.org/bbangert/dozer/get/b748d3e1cc87.gz

Add this to your middleware:

# Add this to your middleware.py, right before return app
    if asbool(config['debug']):
        from dozer import Logview
        app = Logview(app, config)

Add this to your development.ini

# Add to development.ini
logview.sqlalchemy = #faa
logview.pylons.templating = #bfb

(you can customize the colors here)

Next, modify your configuration ini as well as you like to configure what shows up in the log. Note that I have root set to INFO which will squelch a lot of messages. Change this to DEBUG to see more of what's going on in each request.

# Logging configuration
[loggers]
keys = root, YOURPROJ

[handlers]
keys = console

[formatters]
keys = generic

[logger_root]
level = INFO
handlers = console

[logger_YOURPROJ]
level = DEBUG
handlers =
qualname = YOURPROJ.lib

[logger_sqlalchemy]
level = INFO
handlers =
qualname = sqlalchemy.engine

[handler_console]
class = StreamHandler
args = (sys.stderr,)
level = NOTSET
formatter = generic

[formatter_generic]
format = %(asctime)s,%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
datefmt = %H:%M:%S

Add this file to /lib/

querytimer.py

from sqlalchemy.interfaces import ConnectionProxy
import time
 
import logging
log = logging.getLogger(__name__)
 
class TimerProxy(ConnectionProxy):
    def cursor_execute(self, execute, cursor, statement, parameters, context, executemany):
        now = time.time()
        try:
            return execute(cursor, statement, parameters, context)
        finally:
            total = time.time() - now
            log.debug("Query: %s" % statement)
            log.debug("Total Time: %f" % total)
 

Okay, one last thing, modify your SQLAlchemy engine in environment.py to this:

engine = engine_from_config(config, 'sqlalchemy.', proxy=TimerProxy())

and add an import at the top:

from YOURPROJ.lib.querytimer import TimerProxy

So that's it! Restart paster, and load up a request in your web browser. There will now be a bar at the top that you can click on and see all the requests.

If you want to run TimerProxy on it's own (that is without Pylons and Dozer, see zzzeek's post on "Timing All Queries".

November 6, 2008

Burning Man Aerial

This is a stitched image from Burning Man Earth of Burning Man 2008. Unlike my last post, this one isn't straight down, as I wanted to create a dramatic view of the city, and is taken on a different day. At some point we'll have a timelapse from above. How cool is that?

If anyone is interested in wallpapers, let me know.

November 5, 2008

Burning Man Aerial Preview

This is one preview from the many aerial shots of Burning Man from this year, 2008. More to come.

This is the work of Jeffrey of Pict'Earth. The Burning Man Org also contributed to help this project happen. It is part of the Burning Man Earth project (not associated with Google).

November 3, 2008

Nonexistence