#.think.in
learn.create.enjoy

IronPython Asynchronous RSS Reader

January 13, 2009 11:25 by tarn

Reading RSS is a common and simple task in Python using the standard library, unfortunately it uses parts of the standard library that are not implemented in IronPython. Luckily it isn't very difficult using the .NET framework either. There is an example Parsing RSS on the IronPython Cookbook site that demonstrates it. 

While this example was useful, I wanted to make the web request asynchronously so I could use it in a WPF application without blocking the UI. I was happy to find implementing it was quite familiar and easy, I've used the WebClient object asynchronously many times in C# and I've passed event handlers as parameters in Javascript, both of which are supported by IronPython and are used in this module.

In the implementation below I've used all XML processing code from the Parsing RSS example. I removed the class RSSFeedSubscriptions as I didn't need the functionality and I felt it made the sample more clear. I split the RSSFeedFetcher into an RSSFeed class for the feed data and an RSSFeedReader class for handling the downloading and XML processing. I think it is much a better separation of functionality.

I found the the exception handling code in the original sample that outputs an exception message was itself throwing an exception, so I removed it. I don't think its ideal effectively suppressing exceptions and printing a message but its good enough for what I'm using it for. If anyone has a suggestion or I improve the module myself, I'll update this post.   

The example usage is shown in the Main section. I used an AutoResetEvent in the test to keep the tread alive until the asynchronous event is fired and the results are output. 

import clr
clr.AddReference('System.Xml')

from System.Net import WebClient
from System.Xml import XmlDocument, XmlTextReader
from System.IO import StreamReader, MemoryStream
import System

class RSSFeedItem(object):
    def __init__(self):
        self.Title = ""
        self.Description = ""
        self.Link = ""
        self.GUID = ""

class RSSFeed():
    def __init__(self, URL):
        self.FeedURL = URL
        self.ChannelName = ""
        self.Items = []
        self.BadCount = 0        

class RSSFeedReader(object):
    def __init__(self,URL, onComplete):
        self.onComplete = onComplete
        self.feed = RSSFeed(URL)
    def Parse(self, sender, args):
        rssBytes = args.Result
        try:
            xmlDoc = XmlDocument()
            stream = MemoryStream(rssBytes)
            xmlDoc.Load(stream)
            rssNode = xmlDoc.SelectSingleNode("rss")
            channelNodes = rssNode.ChildNodes
            
            for channelNode in channelNodes:
                itemNodes = channelNode.SelectNodes("item")
                
                self.feed.ChannelName = channelNode.SelectSingleNode("title").InnerText
                
                for itemNode in itemNodes:
                    try:
                        newitem = RSSFeedItem()
                        newitem.Title = str(itemNode.SelectSingleNode("title").InnerText)
                        newitem.Description = str(itemNode.SelectSingleNode("description").InnerText)
                        newitem.Link = str(itemNode.SelectSingleNode("link").InnerText)
                        if itemNode.SelectSingleNode("guid"):
                            newitem.GUID = str(itemNode.SelectSingleNode("guid").InnerText)
                        self.feed.Items.append(newitem)
                    except:
                        self.feed.BadCount = self.feed.BadCount + 1
        except:
            print "Error Parsing Results"
        self.onComplete(self.feed)
    def ReadAsync(self):
        try:
            webClient = WebClient()
            webClient.DownloadDataCompleted += self.Parse
            uri = System.Uri(self.feed.FeedURL)
            webClient.DownloadDataAsync(uri)
        except:
            print "Error Starting Read"
        
if __name__ == "__main__":
    from System.Threading import *
    are = AutoResetEvent(False)
    
    def callback(feed): 
        print "Results!"
        for item in feed.Items:
            print item.Title
            print item.Description
            print item.Link
        are.Set()   
        
    feedReader = RSSFeedReader("http://feeds.feedburner.com/sharpthinking", callback)
    feedReader.ReadAsync()

    are.WaitOne()

One of the things I like about IronPython 2 is that this code alone can be run from the command line. Simply saving this code as rssreader.py and running it from the command line with ipy rssreader.py will run the test code at the bottom which will output recent posts from this weblog.


Tags: , ,
Categories:
Comments (3)

AdRotator WebControl Example

June 15, 2008 18:01 by tarn

I didn't really actually want to write another ad rotator, but I did think it would be a good example control to implement. It has the scope to cover some aspects of writing web controls I wanted to learn more about and discuss. I decided to build an ASP.Net web control that cycles through a list of images on the client side. There are a few areas I want to discuss.

I wanted the control completely encapsulated within an assembly. So all you'd need to use the control would be a copy of the assembly. To do this I embedded some resources in the assembly and read them out at runtime. I discuss this in the article.

I wanted the control to be able to be created completely declaratively, and I thought providing the sort of intellisense you get when using standard ASP.Net controls would be nice.

I wanted the server-side control to inject Javascript into the output. I wanted the control to be smart enough to only output the Javascript prototype, or class definition once, even if there were multiple controls on the page. I wanted the client side code to be as object orientated as possible. 

You can download the source code which includes an example website in the solution.

I would like to add a post about supporting difference storage mechanisms and using the control, but I have other projects demanding my attention. Hopefully I'll get back to it sometime.

I Hope you've found some of these articles useful or interesting. I'll keep and eye on the comments, contribute to further discussion, and update the posts where errors or omissions are noted.

Cheers