#!/usr/bin/env python """ Feed: class definitions for an RSS feed. My goal with this module was to make everything extremely simple from the user/programmer point of view. This comes out in two ways: * It's very, very simple to use, and * It holds your hand a little. My wife has just wandered in and informed me that she's not that happy with the amount of time I'm spending on the computer this weekend, so I'm going to have to explain both of these a little later. :) """ __version__ = "$Revision: 1.3 $" __author__ = "Garth T Kidd " import rssparser import time class NoneAtt(object): "`NoneAtt`s gently respond to attempts to read non-existent attributes." def __getattr__(self, key): "Default attribute gets to None, to save on try..excepts" try: return object.__getattribute__(self, key) except AttributeError: return None class ChannelDetails(NoneAtt): "Channel details." def __init__(self, pollDict): "Initialise ChannelDetails from `rssparser.parse` results." self.__dict__.update(pollDict['channel']) if self.date is not None: self.timestamp = time.mktime(rssparser.parse_http_date(self.date)) class PollDetails(NoneAtt): "Poll details." def __init__(self, pollDict): "Initialise PollDetails from an `rssparser.parse` results." self.items = [] pd = pollDict.copy() # avoid modifying our argument if pd.has_key('items'): for itemDict in pd['items']: self.items.append(ItemDetails(itemDict)) del pd['items'] if pd.has_key('channel'): self.channel = ChannelDetails(pollDict) del pd['channel'] self.__dict__.update(pd) # copy remaining attributes class ItemDetails(NoneAtt): "Item details." def __init__(self, itemDict): "Initialise ItemDetails from an item in `rssparser.parse` results." id = itemDict.copy() # avoid modifying our argument if id.has_key('date'): # parse the date to `.timestamp` attribute, leaving the original. self.timestamp = time.mktime( rssparser.parse_http_date(id['date'])) self.__dict__.update(id) class Feed(object): "An RSS feed." def __init__(self, uri, pollLater=0): """ Initialise the channel. uri -- the URI of the channel. pollLater -- poll the channel later. """ self.uri = uri self.polls = [] self.latestPoll = None self.channel = None # most recent channel details if not pollLater: self.poll() def __getattr__(self, key): """Act as a proxy for poll and channel attributes. `self.etag`, for example, is pulled from the latest poll if there's a latest poll *and* it has an `etag` attribute.""" if self.latestPoll is not None and key in [ 'etag', 'modified', 'items']: return getattr(self.latestPoll, key) elif self.channel is not None: return getattr(self.channel, key) else: return None # still, these gentle responses! def poll(self): "Poll our channel and absorb the results." results = rssparser.parse(self.uri, etag = self.etag, modified = self.modified) # Thanks, Mark! if not (results['channel'] or results['items']): # Not modified. return pollDetails = PollDetails(results) self.latestPoll = pollDetails self.polls.append(pollDetails) self.channel = pollDetails.channel if __name__ == '__main__': from SimpleStripper import strip, truncate feed = Feed('http://www.deadlybloodyserious.com/rss.xml') for att in ['title', 'link', 'creator', 'description', 'language', 'rights', 'date', 'timestamp', 'etag', 'modified']: print "%s: %s" % (att, repr(getattr(feed, att))) num = 0 for item in feed.items: num += 1 title = summary = '' if item.title is not None: title = ' '.join(strip(item.title)) if item.description is not None: parasWithContent = [p for p in strip(item.description) if p] # join takes care of zero-length lists quite nicely firstPara = ' '.join(parasWithContent[0:1]) summary = truncate(firstPara, 75) if not summary: summary = '(no description)' print "\nItem #%d: %s\n%s" % (num, repr(title), summary)