Skip to main content

skip to main content

developerWorks  >  SOA and Web services  >

Getting started with PyXPCOM, Part 2

Accessing objects as a client

developerWorks
Document options

Document options requiring JavaScript are not displayed


Rate this page

Help us improve this content


Level: Introductory

Uche Ogbuji (uche@ogbuji.net), Principal Consultant, Fourthought, Inc.

01 Jun 2001

Cross-platform component object model (XPCOM) is the component system developed in the Mozilla project. ActiveState has developed an open-source Python library for XPCOM. This three-part series provides a developer's introduction to XPCOM programming in Python. This second part covers the use of PyXPCOM as a client to XPCOM objects.

Now that you have PyXPCOM installed, you'll find Mozilla scripting and general XPCOM development easier than ever. At this point I suggest you read through a good Mozilla scripting tutorial, especially Neil Deakin's excellent tutorial on Mozilla's XML-based User-interface Language (XUL). Among many other things, Deakin's tutorial covers how to connect to components of the Mozilla project itself. His tutorial is out of date in places, so I would still spend some time in the official documentation of the particular components you're using.

Interfaces with a touch of class

Neil Deakin's XUL tutorial deals with Javascript rather than Python as scripting language, but the PyXPCOM developers purposefully made the Python binding as similar as possible to the Javascript binding. This similarity is a great boon to Python users familiar with Mozilla Javascript programming, and to those who have been through Mozilla Javascript tutorials. First of all, we'll look at the Python form of some of the Javascript examples in Neil Deakin's tutorial.

We'll start off in the Python interactive console. By simply entering:

>>> from xpcom import components

we obtain the components singleton object, which is equivalent to the Components object in Javascript (note the different capitalization). This object represents the XPCOM component registry, allowing access to all the factories and interfaces. The classes attribute of the components object is a dictionary of all the classes registered with XPCOM. The key is the contract ID, and the value is the class object, which also acts as a factory. Therefore, we can invoke the createInstance() method on the class object to create a new instance of the class.

Working with classes and interfaces

Mozilla comes with a class representing local file objects. As an example, a script could use this interface to save downloaded data to files for the user. In the following example we retrieve the class object according to the appropriate contract ID, then create a new instance from the class object.

>>> file_cls = components.classes["@mozilla.org/file/local;1"]
>>> print file_cls
<xpcom.components._Class instance at 0x823c504>
>>> file_abs = file_cls.createInstance()
>>> print file_abs
<XPCOM interface 'nsISupports'>

Notice that the resulting instance is identified as "interface nsISupports". Actually, what we have is a Python proxy object that carries this interface. Remember that this is simply a generic interface that all XPCOM objects provide. To actually call file methods on the object we've created, we must get the appropriate interface. We do this by calling queryInterface(), which returns an object with an interface that allows us to call methods for a local file object. To do this we have to pass in the interface object for local files. The components object also has an interfaces attribute which has one attribute for each registered interface; the attribute name is the interface name used in the IDL. The interface for a local file is nsILocalFile, and its interface ID is the universally unique ID (UUID) "aa610f20-a889-11d3-8c81-000064657374".

>>> file_if = components.interfaces.nsILocalFile
>>> print file_if
{aa610f20-a889-11d3-8c81-000064657374}
>>> file = file_abs.queryInterface(file_if)
>>> print file
<XPCOM interface 'nsILocalFile'>

Invoking object functions

Now that we have a local file object with the proper interface, we can put it to work. A local file object isn't tied to any particular system file at first. You make this association by invoking the initWithPath() method. So, for instance, if we want to delete the file "/tmp/spam.msg", we would create a local file instance, as above, and then initialize it with the file name of interest.

>>> file.initWithPath("/tmp/spam.msg")
 

If you're experimenting, note that the argument to initWithPath() must be a complete file path. If you pass a relative file path such as "spam.msg", you'll get an error. Now we can invoke the delete method to do our deed.

>>> file.delete(0)

The delete method from the nsILocalFile IDL is as follows:

    /**
     *  This will try to delete this file.  The 'recursive' flag
     *  must be PR_TRUE to delete directories which are not empty.
     *
     *  This will not resolve any symlinks.
     */
    void delete(in boolean recursive);

The recursive argument makes sense when you realize that the nsILocalFile interface can represent directories as well as files. As you can see, we passed in the integer 0 for this boolean value. PyXPCOM does a great job of representing the XPCOM data model in a natural way for Python. Python does not have a boolean type, but it treats certain values as false (such as None; empty strings, sequences and mappings; and numbers with zero value). It treats all other values as true. PyXPCOM uses these familiar rules to coerce Python arguments and return values to appropriate boolean values for XPCOM. See the PyXPCOM documentation for the full set of data model rules.

The Python advantage

One of the lovely things about doing any development in Python is the flexibility and cleanliness of its support for dynamic programming. Python is famous for making rapid development in Java actually possible, and PyXPCOM brings the same benefit to XPCOM. One particular aspect of this is the way it makes experimentation at the interactive console so simple.

We have a local file object, but how do we know what to do with it? We can read the IDL and all the documents, but we can also take a quick peek directly at the object. PyXPCOM objects have an _interface_methods_ attribute which is a dictionary of all the available methods. So, for instance, we can do the following:

>>> print file._interface_methods_.keys()
['delete', 'isFile', 'initWithPath', 'isReadable', 'reveal', 
'QueryInterface', 'copyTo', 'copyToFollowingLinksUnicode', 'contains', 
'isWritable', 'launch', 'isSpecial', 'isHidden', 'normalize', 
'append', 'isExecutable', 'create', 'moveToUnicode', 'createUnique', 
'appendRelativeUnicodePath', 'initWithUnicodePath', 'isDirectory', 
'appendRelativePath', 'equals', 'copyToFollowingLinks', 'clone', 
'copyToUnicode', 'isSymlink', 'exists', 'appendUnicode', 'moveTo', 
'spawn']

And presto! We at least know the name of all the available methods. By getting the value from this dictionary, you get a function object with attributes that you can access for further introspective goodies.

I must warn, however, that these attributes with leading underscore are, as in the Python tradition, private, and unsafe to use for anything more than occasional, interactive prodding. They may change name without notice, and they may change behaviour without notice. Of course you can always use the magic function dir() and the magic attribute __dict__ to find out what private variables are available for probing.



Back to top


Dial PyXPCOM for service

XPCOM services are accessible through PyXPCOM as special singleton objects. To access a service you first get the class object using the contract ID. But you can't call createInstance() on the classes because there is only one true instance against which all requests are made. Instead, you use the getService() method to retrieve a proxy to the service. You pass this method the appropriate interface according to your needs.

As an example, let's access the Mozilla service that provides an interface to user preferences:

>>> from xpcom import components
>>> ps_cls = components.classes["@mozilla.org/preferences-service;1"]
>>> ps = ps_cls.getService(components.interfaces.nsIPrefService)
 

Note that Neil Deakin's tutorial wrongly omits the interface argument to getService() in his example. Now we can piggyback on all the great code the Mozilla developers wrote for user preference management. First of all we read the current user preferences from the configuration file:

>>> ps.readConfigFile()

The configuration settings are arranged in a hierarchy, and we must access its branch to read or update a particular entry. The branches are identified by identifiers separated by dots.

>>> branch = ps.getBranch("browser.startup.")
>>> print branch.getChildList("")
['browser.startup.homepage', 'browser.startup.license_accepted', 
'browser.startup.page', 'browser.startup.autoload_homepage', 
'browser.startup.homepage_override.1']

As we can see from the "browser.startup." root, which deals with the behavior of a browser window when first launched, we get information such as the initial home page and whether or not the user has accepted the Mozilla license. We used the getChildList() method which lists all the names of settings within the branch. Let's have a look at one of them:

>>> print branch.getCharPref("homepage")
chrome://navigator-region/locale/region.properties

You may get a different URL if you have changed Mozilla's initial home page. The getCharPref() method is used to get the value of settings with string value; it takes the name (without root) of the setting as its only argument. Note that you can determine the type of a setting by using the getPrefType() method, which returns an integer representing the type of the given setting. You can add or update a setting in similar fashion:

>>> branch.setCharPref("homepage", "http://ibm.com/developer")

File component + preference component = preference file

As an example of how XPCOM components work together, listing 1 is a Python program that reads in the Mozilla configuration, modifies the browser startup screen, and writes the result of the update to a temporary file ("/tmp/foo.prefs").


Listing 1: Update a user preference settings and write the update to a temporary file
from xpcom import components

#Get the Mozilla preference service
ps_cls = components.classes["@mozilla.org/preferences-service;1"]
ps = ps_cls.getService(components.interfaces.nsIPrefService)

#Use the service to read the default configuration file
ps.readConfigFile()

#Set the setting "browser.startup.homepage" to 
"http://ibm.com/developer"
branch = ps.getBranch("browser.startup.")
branch.setCharPref("homepage", "http://ibm.com/developer")

#Get a local file instance
file_cls = components.classes["@mozilla.org/file/local;1"]
file_abs = file_cls.createInstance()
file = file_abs.queryInterface(components.interfaces.nsILocalFile)

#Set up the new file path
file.initWithPath("/tmp/foo.prefs")

#Create the file using UNIX permissions mode of "775" ("-rw-rw-r--")
file.create(file.NORMAL_FILE_TYPE, 511)

#Save the updated preferences to the new file
ps.savePrefFile(file)



Back to top


Next: roll your own components

Of course, as with any rich technology, there is always more to discover with PyXPCOM, but this discussion gives you a sound footing on which to continue your further exploration with the PyXPCOM and Mozilla docs and other available resources. In the next and final article in this series we'll discuss how to implement XPCOM objects in Python.



Resources



About the author

Uche Ogbuji is a consultant and co-founder of Fourthought, Inc., a software vendor and consultancy specializing in XML solutions for enterprise knowledge management. Fourthought has developed 4Suite, an open source platform for XML, RDF, and knowledge-management applications. Uche is a computer engineer and writer born in Nigeria; he lives and works in Boulder, Colorado. You can contact Uche at uche@ogbuji.net.




Rate this page


Please take a moment to complete this form to help us better serve you.



YesNoDon't know
 


 


12345
Not
useful
Extremely
useful
 


Back to top