Saturday, October 10, 2009

KDE Hello World

I have plans to do a web radio player (I wonder if we'll ever see this app). My intention is to learn Qt/KDE programming in Python. I've played around with Qt a bit and I'm starting to get comfy. I've decided to try doing the player as a KDE app, just to learn more about KDE. Qt is great for doing cross-platform stuff but I want to take a look at what KDE offers on top of Qt. You can find docs, tutorials and more on KDE TechBase. The following snippet shows the minimum amount of code needed to create a typical KDE app in Python.
from PyKDE4 import kdecore
from PyKDE4 import kdeui
import sys

def createAboutData():
    """
    Create a KAboutData with information about this application.
    """
    return kdecore.KAboutData(
            # Program name used internally
            "hello",
            
            # Catalog name
            "",                       
            
            # Displayable program name
            kdecore.ki18n("Hello World app"),
            
            # Program version
            "0.1.0",
            
            # Short description about the program
            kdecore.ki18n("A simple KDE Hello World app"),
            
            # Program license
            kdecore.KAboutData.License_BSD,
            
            # Copyright statement
            kdecore.ki18n ("(c) 2009 Mario Boikov"),
            
            # Free form text
            kdecore.ki18n("Free form text\nsupporting newlines"),
            
            # Home page address for this program
            "http://www.pysnippet.com",
            
            # Bug report email address
            "mario@beblue.org",
            )

def main():
    about = createAboutData()
    kdecore.KCmdLineArgs.init(sys.argv, about)

    app = kdeui.KApplication()

    # INSERT APP CODE HERE

    # Start event loop
    sys.exit(app.exec_())

if __name__ == '__main__':
    main()
Nothing special will happen if you execute this application, we need some kind of window/widget to display something. To accomplish this we can create a KMainWindow with a KPushButton. We'll also add code to print Hello World when the button is clicked. The following snippet will create a window with a push button:
from PyKDE4 import kdeui
from PyKDE4 import kdecore
from PyQt4 import QtCore

class MainWindow(kdeui.KMainWindow):
    def __init__(self):
        super(MainWindow, self).__init__()

        # Create widget with vertical box layout manager
        layout = kdeui.KVBox()

        # Create push button and add to layout
        button = kdeui.KPushButton(kdecore.i18n("Push Me!"), layout)

        # Connect to button's 'clicked' signal 
        QtCore.QObject.connect(button, QtCore.SIGNAL("clicked()"),
                               self.buttonClicked)

        self.setCentralWidget(layout)

    def buttonClicked(self):
        print "Hello World!"
You might wonder why I use ki18n() when creating the KAboutData and i18n() for the MainWindow. Both functions do translations, i18n() requires an instance of KApplication before use but ki18n() don't.

To actually show the window we have to instantiate it and make it visible, the "INSERT APP CODE HERE" comment should be replaced with the following lines:
    mainWindow = MainWindow()
    mainWindow.show()
As you can see, it doesn't take too much effort to create a simple app in KDE. There's almost more code to create the KAboutData than doing the window with a push button. Here's a screen shot of what the window looks like on my Kubuntu installation:



If we compare Qt and KDE there aren't too much differences, at least with this example. The code below implements the same Hello World app made with Qt:

import sys
from PyQt4 import QtCore, QtGui

class MainWindow(QtGui.QMainWindow):
    def __init__(self, title):
        super(MainWindow, self).__init__()
        self.setWindowTitle(title)

        centralWidget = QtGui.QWidget()

        button = QtGui.QPushButton("Push Me!")

        layout = QtGui.QVBoxLayout()
        layout.addWidget(button)

        centralWidget.setLayout(layout)

        QtCore.QObject.connect(button, QtCore.SIGNAL("clicked()"),
                               self.buttonClicked)

    self.setCentralWidget(centralWidget)

    def buttonClicked(self):
        print "Hello World!"

def main():
    app = QtGui.QApplication(sys.argv)

    mainWindow = MainWindow("Hello World App")
    mainWindow.show()

    sys.exit(app.exec_())

if __name__ == "__main__":
    main()

The biggest difference is how the layout is handled and the absence of About Data.

Wednesday, October 7, 2009

Python and unit testing

Writing unit tests in python is really easy. It's the dynamic properties of the language which makes it very simple to mock class methods, instance methods and functions. This makes the task of writing isolated unit tests easy when you practice test-driven development.

Say you have the following class:
import urllib2

class UrlDownloader(object):
    def download(self, url):
        """
        Read the content from the url.
        The content is returned as a string object.
        """
        return urllib2.urlopen(url).read()
You actually don't want to download something from the net when you do a unit test for this class. You would like to have a fixed string returned from the read() method. This can be hard to achieve in some languages (for example Java) but that's not true for Python. Python supports redefining class methods, functions and other things in run-time. The following snippet shows how easy it's to mock the urlopen() function in the urllib2 module to return a mocked file-like object:
import urllib2
import unittest

class MockFileObject(object):
    def read(self):
        return "DATA"

def mock_open(url):
    return MockFileObject()

class UrlDownloaderTest(unittest.TestCase):
    def testDownload(self):
        urllib2.urlopen = mock_open
        dl = UrlDownloader()
        self.assertEqual(dl.download(""), "DATA")

if __name__ == '__main__':
    unittest.main()
In this unit test we redefine the urlopen-function (remember, this is not a member of an instance, it's a plain function) with our own which returns a mock file-like object. It's not that hard to imagen all the possibilities this gives when writing tests. There are of course frameworks that will help you with creating mocks etc, you could try The Python Mock Module which should cover most of the use cases.

Friday, October 2, 2009

File-like objects

A Python file-like object can be seen as an abstraction to input/output streams. They are actually similar to Java's java.io.Reader/java.io.Writer abstraction (I'm coming from Java). The typical usage of file-like objects can be found in frameworks and APIs. The file-like object provides a generic way of reading/writing data and would fit an HTML-parser for example. The parser is not interested of where the data come from (files, sockets, etc), only the content, so naturally the implementation should not depend on how to open resources etc.

Basic methods that need to be implemented by a file-like object are:
  • read([size]) - read at most size bytes from the file.
  • readLine([size]) - read one entire line from the file.
  • write(str) - write a string to the file.
  • close() - close the file.
You should read the documentation of the function/method that uses the file-like object, often they specify which methods that are expected to be implemented by the file-like object. If the file-like object is only used for reading, no write-functions need to be supported. More information about file-like objects can be found here.

Ok, so why should you use file-like objects in your code?

Let's take a look at the ConfigParser class which can be used to read/write configuration data. You can expect to find methods for reading and writing configuration data using files but the class does also include support for file-like objects, which is great. For example it's very easy to write unittests for this class, thanks to the support of file-like objects,  without having to create real files with test data. To create a file-like object from a string we can use the StringIO module. The module have both read and write support.

Here's some code:
import unittest
import StringIO
import ConfigParser

class ConfigParserTest(unittest.TestCase):

    def testHasSection(self):
        """
        has_section() should return True if the section exists
        and False if is doesn't.
        """
        data = StringIO.StringIO("[My section]\nSetting=1\n")
        config = ConfigParser.ConfigParser()
        config.readfp(data)
        self.assertTrue(config.has_section("My section"))
        self.assertFalse(config.has_section("Not a section"))

    def testMissingHeader(self):
        """
        readfp() should raise MissingSectionHeaderError
        when parsing a file without section headers.
        """
        data = StringIO.StringIO("\nSetting=1\n")

        config = ConfigParser.ConfigParser()
        self.assertRaises(ConfigParser.MissingSectionHeaderError,
                          config.readfp, data)

if __name__ == "__main__":
    unittest.main()
And a simple write configuration test:
import unittest
import StringIO
import ConfigParser

class WriteConfigTest(unittest.TestCase):

    def testWriteConfig(self):
        """
        Test write a simple config
        """
        data = StringIO.StringIO()
        config = ConfigParser.ConfigParser()
        config.add_section("My Section")
        config.set("My Section", "my_option", "my_value")
        config.write(data)

        expected = "[My Section]\nmy_option = my_value\n\n"
        self.assertEquals(expected, data.getvalue())

if __name__ == "__main__":
    unittest.main() 
These unittests are of course only examples and do not fully test the ConfigParser class.
 
It's easy to see that file-like objects are very handy and fits well in frameworks and APIs. There are of course many advantages of using an abstraction when reading/writing data, some of them are:
  • Implementations do not depend on real files, socket, etc, only on a file-like object.
  • Easier to fake data in unittests.
  • Caller responsible of opening/closing the resource.
  • Less error-handling in framework code, delegate exceptions to the caller.
The downside with the current file-like object abstraction is that it's tailored for text I/O. In Python 2.6 and 3.x a new module have been added named io. The module originates from the New I/O PEP. The module enriches Python with an three-layer solution, the raw I/O layer, the buffered I/O layer and finally the text I/O layer. The raw and buffered layers deals with units of bytes and the text layer deals with units of characters. There are some things you should be aware of if you use the new I/O library in Python 2.6, read more about it here.

I've implemented a shoutcast playlist parser, plsparser, using the ConfigParser class. The parser is implemented as a Python generator function. Actually, I might do a post about generator functions someday.