The PyKDE Components Framework

Contents

Introduction

The PyQt and PyKDE wrappers for the Qt library and KDE frameworks provide wonderful assistance for the application and tool developer. However, the role of Python within each framework need not end at the application level; the nature of the interface between the underlying C++ objects and their Python counterparts allows for much more flexible interaction. Since the interface needs to provide two way communication between the two levels, it is possible to deploy an embedded Python interpreter within a Qt or KDE application. Such an interpreter can interact with the application and surrounding framework in much the same way as an ordinary Python application; only the means of deploying Python in an embedded form is a substantial barrier to its use in this manner.

Recently, various solutions to this problem have become available thanks to collaborative work by participants on the PyKDE mailing list. In particular, work by Jim Bublitz on embedding Python in shared library form has allowed a variety of components to be written in Python and successfully used alongside their C++ counterparts.

In this document, we describe the development of a framework that allows components written in Python to be deployed in a manner that is at least as straightforward as that for their C++ equivalents.

Latest changes

2004-01-18: Many Python modules no longer need to be explicitly declared in the module requirements dictionary. This information was previously used when building each component's support library to ensure that the embedded Python interpreter could find shared library modules at run time.

Background and Motivation

Prior to PyKDE 3.8 there was no convenient way to develop components for KDE's infrastructure using Python, despite having wrappers for all the relevant KDE classes. This situation began to change with the introduction of a simple build system for IOSlaves which started a wider investigation into support for other forms of embedded components. Over time, it has become possible to see similarities between the many different types of components and the mechanisms used to integrate them with the KDE infrastructure. Generally speaking, the types of components we are interested in writing in Python are deployed in the form of shared libraries (.so files) which reside in standard locations within the directory structure of a KDE installation. These libraries are made available to applications and other components through a registration process involving files which describe their capabilities (.desktop files).

The build system for IOSlaves has therefore evolved to take advantage of these similarities and has become general enough to deal with a number of different types of components:

To the experienced KDE developer, who is proficient in C++, it may seem strange to wish to allow these tasks to be performed in an interpreted, dynamically-typed language such as Python. However, the use of Python in at least some of these contexts brings a number of advantages:

Construction

Components are made up of a number of pieces: the main requirements are a library, written in C++, and a Python module which contains some classes derived from those supplied with PyKDE. Depending on the component under construction, there will be a variety of support files which describe the functions and services of the component; these include .desktop files, XMLGUI .rc files and icons.

These pieces are assembled by a build script which uses information provided for each component. Building and installing a component happens in the following manner:

Common features

Similarities between various forms of component allow the contents of the details.py file to be fairly general, yet provide specialised information when necessary. To illustrate the common elements of this description, we will use parts of the details.py file for the ShellPreview ThumbCreator plugin for generating thumbnail previews for files in Konqueror.

This file must contain a function called find_details. This is given in the excerpts contained in the following sections.

Module details

The first declaration of the function provides general information about the component in a dictionary:

def find_details():

    details = \
    {
        # Project name - the filename of the Python module should
        # be derived from this: e.g. DefaultPython -> defaultpython.py
        "name":             "ShellPreview",
        
        # A pretty name for this module and its description
        "pretty name":      "Sketch related files",
        "comment":          "Previews Sketch related files",
        
        # Change this function or class name in your Python module to match
        # the name of the factory class.
        "factory":          "ShellPreviewClass",
        
        # Destination filename in each destination for the icons described
        # below.
        "icon":             "shellpreview.png"
    }

The entries perform the following functions:

Module requirements

The requirements of the component are described by a second dictionary:

    requires = \
    {
        "library":     "py_thumbcreator.cpp",
        
        # Specify the PyKDE libraries required by this applet.
        "pykde":        ["kio"],
        
        # Specify the PyQt libraries required by this applet.
        "pyqt":         ["qt"],
        
        # Specify other libraries beneath the Python library directory.
        # A None value for the path causes the module to be searched for
        # within the Python library directories.
        "python":       [],
        
        # Specify other items to include alongside the module in its own
        # directory.
        "other items":  []
    }

In this dictionary, the entries are used for the following purposes:

Fortunately, it is no longer necessary to declare other shared library modules at compile time. This means that many modules from the standard Python distribution can be used as normal from within PyKDE components.

However, it may be necessary for particular Python modules to be explicitly declared at compile time. Since Python modules can reside in a number of places within the Python library directory, this entry provides a mechanism which allows the path to the module to be declared in the form of a list of tuples. Each tuple contains the name of the module (without a .py suffix) and the name of the subdirectory within the Python library directory.

For example, ("time", "lib-dynload") will cause the time module to be made available at run time. For modules which may be found in a variety of places within the Python library directory, a value of None may be given for their path values. This instructs the build process to search for suitable candidates, but a cautionary example should be given: use of the tuple ("time", None) will find the time module supplied with PyGame before it find the module in lib-dynload. The subdirectories of the Python library directory may be searched in a more careful manner in later versions of the build tool.

Supplied resources

Components use a number of items which are provided by the developer or are generated when the component is built and installed. A dictionary is declared for this purpose, too:

    provides = \
    {
        ".py":
        {
            "filename":     string.lower(details["name"]),
            "directory":    string.lower(details["name"]),
            "destination":  "%(kde dir)s/share/apps"
        },
        
        ".so":
        {
            "name":         string.lower(details["name"]),
            "filename":     "lib" + string.lower(details["name"]),
            "destination":  "%(kde dir)s/lib/kde3"
        },
        
        ".la":
        {
            "contents":     linking_template,
            "filename":     "lib" + string.lower(details["name"]),
            "destination":  "%(kde dir)s/lib/kde3"
        },
        
        ".desktop":
        {
            # Give the path beneath which the desktop file should be stored.
            "filename":     string.lower(details["name"]),
            "destination":  "%(kde dir)s/share/services",
            "contents":     desktop_template
        },
    }

Each subdirectory in the above excerpt describes the information needed to install each resource in the correct location. For generated content, such as the .desktop file, the entry refers to an object which is typically declared outside the find_details function.

Much of these subdictionaries need little modification by developers, but it is worth summarising their entries.

The ".py" entry represents the Python source file which provides the component's functionality. This should have been supplied by the developer. Its locations in build and installation directories are defined in the subdirectory provided:

As for the entry describing the Python source file, the ".so" entry describing the library file provides values for the filename and destination. However, it does not support the creation of new directories. It is necessary to provide a separate "name" entry for the library name. This is used to specify the filename in a form suitable for certain library loading mechanisms and its form may vary between each type of component.

The ".la" entry describing the linking script provides values for both the filename and the destination. Since the linking script is not currently generated by the library build process itself, we provide a script which is generated from a template. The "contents" entry refers to such an object which is typically declared outside the find_details' function.

The ".desktop" entry provides the same type of information as the linking script; the filename and destination entries are accompanied by a "contents" entry which refers to a template declared elsewhere.

Returning information

After the information describing the component has been declared, the find_details function returns all the information required to build and install the component:

    return details, requires, provides

However, this information relies on other sources which we have referenced, but which we have yet to provide.

Additional information

For this example, we need only supply templates for the .desktop and .la files. The former usually requires some customisation by the developer, but the latter is best left as supplied and, as such, we do not describe it here.

The template for the .desktop file used in our example is declared in the following manner:

desktop_template = \
"""[Desktop Entry]
Encoding=UTF-8
Type=Service
Name=%(name)s
ServiceTypes=ThumbCreator
MimeTypes=image/x-sketch,image/x-drawfile,application/x-impression
CacheThumbnail=true
X-KDE-Library=%(lib)s
"""

This looks like many of the files included in any standard KDE distribution but, to assist the build process, we have used some string substitutions commands in the text; these take the form %(...)s and refer to dictionary entries related to those which we described in the above dictionaries:

Conclusion

The above mechanism for specifying and installing components is sufficient for the current set of requirements and supported types of components. However, the scheme may need changing in the near future as the components framework receives wider publicity. Since changes to the scheme will affect existing code and component descriptions, it is preferable that any modifications should be proposed sooner rather than later.