Python-UNO bridge

- Translations
- Introduction
- State
- Download
- Tutorial
-- PyUNO Installation
-- PyUNO Bridge Modes
-- More examples
- UNO language binding
-- UNO type mapping
-- Implementing UNO objects
-- Implementing UNO components
-- Out parameter handling
-- Exception handling
-- unohelper module
- Dependencies
- Bootstrapping in non-OOo environments
- Building from source
- Replacing the python runtime
- Support for the new scripting framework
- External references
- FAQ (read this FIRST when you have problems)
- Authors

Translations

Find here a shortened spanish version of this document.

Introduction

The Python-UNO bridge allows to use the standard OpenOffice.org API from the well known python scripting language. It addtionally allows to develop UNO components in python, thus python UNO components may be run within the office process and e.g. can be called from Java, C++ or the built in StarBasic scripting language.

You should find the most current version of this document at http://udk.openoffice.org/python/python-bridge.html

Download

You can download this documentation including examples and support for the new scripting-framework for offline work.

Download pyuno-doc.zip ( less than 0.5 MB).

State

The Python-UNO bridge is feature complete, but has not been used by many people up to now, so it may contain some bugs. It is now integrated in the OpenOffice.org 1.1 source tree. It does not run with the OpenOffice.org 1.0.x source tree.

The documentation in its current state is targeted at developers, who have already some experience with OpenOffice.org API and with some other programming language (Java/C++/StarBasic). If you are a newbie and you think, that some background information is missing, you should try the developer manual mentioned in the external reference section.

PyUNO tutorial for OpenOffice

This tutorial shows, how the PyUNO bridge can be used to automate OpenOffice. This is not an OpenOffice tutorial, there is lots of resources available in the office development kit and the developer manual.

PyUNO Installation

With OpenOffice1.1RC4 and higher, PyUNO is included in the default installation. You can skip to the next paragraph.

When you use OpenOffice1.1RC3 or earlier, the PyUNO-Bridge must be explicitly selected during OOo installation. Choose therefor customized setup,

and activate the PyUNO bridge in the Optional Components section

You can also install PyUNO after the initial installation by starting setup within the office installation and selecting Modify in the upcoming dialog.

Note: When you choose the install script (e.g. install --prefix=/usr/local) for installation, you can only activate pyuno after the installation by starting setup /net and choosing Modify. Note, that in OOorc2, there is a bug which does not install two .ini files as they should in this case.

PyUNO bridge modes

PyUNO can be used in two different modes
  1. Inside the python executable (and outside the OOo process)

    Use this mode, when you

    Hello World

    Make sure, that the office doesn't run (note that on windows you must also terminate the quickstarter in the system tray at the right bottom of your desktop). Start a system shell ( cmd on Win NT/2000/XP, command on Win9x, tcsh or bash on unix). Switch to the Office program directory (e.g. C:\Program Files\OpenOffice.org1.1\program ) and start the office with the following command line parameters

    
    c:\Program Files\OpenOffice1.1\program>  soffice "-accept=socket,host=localhost,port=2002;urp;" 
    
    

    Now use your favourite text editor to create the following hello_world.py sample program:
    import uno
    
    # get the uno component context from the PyUNO runtime
    localContext = uno.getComponentContext()
    
    # create the UnoUrlResolver 
    resolver = localContext.ServiceManager.createInstanceWithContext(
    				"com.sun.star.bridge.UnoUrlResolver", localContext )
    
    # connect to the running office 				
    ctx = resolver.resolve( "uno:socket,host=localhost,port=2002;urp;StarOffice.ComponentContext" )
    smgr = ctx.ServiceManager
    
    # get the central desktop object
    desktop = smgr.createInstanceWithContext( "com.sun.star.frame.Desktop",ctx)
    
    # access the current writer document
    model = desktop.getCurrentComponent()
    
    # access the document's text property
    text = model.Text
    
    # create a cursor
    cursor = text.createTextCursor()
    
    # insert the text into the document
    text.insertString( cursor, "Hello World", 0 )
    
    # Do a nasty thing before exiting the python process. In case the
    # last call is a oneway call (e.g. see idl-spec of insertString),
    # it must be forced out of the remote-bridge caches before python
    # exits the process. Otherwise, the oneway call may or may not reach
    # the target object.
    # I do this here by calling a cheap synchronous call (getPropertyValue).
    ctx.ServiceManager
    

    Now start the above script with the python script located in the program directory
    
    c:\Program Files\OpenOffice1.1\program> .\python hello_world.py 
    
    Note: You must use the script/batch file in the program directory to start python, simply starting the python executable in the runtime directory (or from python installation installed somewhere else on your machine) will not work.

    This scripts prints "Hello World" into the current writer document.

  2. Inside the OpenOffice.org (OOo) process

    Use this mode, when

    Hello World

    The above Hello World example is now recoded as a python UNO component, which means, that the code that does the insertion needs to be embedded in a python class. Additionally, the connecting-to-the-office-code needs to be replaced by a distinct entry point, which is used by the python loader to instantiate the python class.

    hello_world_comp.py:
    import uno
    import unohelper
    
    from com.sun.star.task import XJobExecutor
    
    # implement a UNO component by deriving from the standard unohelper.Base class
    # and from the interface(s) you want to implement.
    class HelloWorldJob( unohelper.Base, XJobExecutor ):
        def __init__( self, ctx ):
            # store the component context for later use
            self.ctx = ctx
            
        def trigger( self, args ):
            # note: args[0] == "HelloWorld", see below config settings
        
            # retrieve the desktop object
            desktop = self.ctx.ServiceManager.createInstanceWithContext(
                "com.sun.star.frame.Desktop", self.ctx )
    	    
            # get current document model
            model = desktop.getCurrentComponent()
    
    	# access the document's text property
    	text = model.Text
    
    	# create a cursor
    	cursor = text.createTextCursor()
    
    	# insert the text into the document
    	text.insertString( cursor, "Hello World", 0 )
    
    # pythonloader looks for a static g_ImplementationHelper variable
    g_ImplementationHelper = unohelper.ImplementationHelper()
    
    # 
    g_ImplementationHelper.addImplementation( \
    	HelloWorldJob,                        # UNO object class
    	"org.openoffice.comp.pyuno.demo.HelloWorld", # implemenation name
    	                                      # Change this name for your own
    					      # script 
    	("com.sun.star.task.Job",),)          # list of implemented services
    	                                      # (the only service)
    

    The code needs to be linked to a user event. This can be done e.g. with the following configuration settings :

    Addons.xcu:
    <?xml version="1.0" encoding="UTF-8"?>
    <oor:node xmlns:oor="http://openoffice.org/2001/registry"
                 xmlns:xs="http://www.w3.org/2001/XMLSchema"
    	     oor:name="Addons" oor:package="org.openoffice.Office">
    <node oor:name="AddonUI">
     <node oor:name="AddonMenu">
      <node oor:name="org.openoffice.comp.pyuno.demo.HelloWorld" oor:op="replace">
       <prop oor:name="URL" oor:type="xs:string">
         <value>service:org.openoffice.comp.pyuno.demo.HelloWorld?insert</value>
       </prop>
       <prop oor:name="ImageIdentifier" oor:type="xs:string">
        <value>private:image/3216</value>
       </prop>
       <prop oor:name="Title" oor:type="xs:string">
        <value xml:lang="en-US">Insert Hello World</value>
       </prop>
      </node>
     </node>
    </node>
    </oor:node>
    
    Both files must be packaged up into a single zip file by using your favourite zip utility, e.g. infozip
    zip hello_world.zip Addons.xcu hello_world_comp.py
      adding: Addons.xcu (deflated 55%)
      adding: hello_world_comp.py (deflated 55%)                                                      
    This package can then be deployed into an OpenOffice installation using the pkgchk tool, which is located in the OOo program directory. Note, that the office must have been terminated before.

    Note: Make sure, that the PYTHONPATH environment variable is NOT set when you start pkgchk or soffice (see #i17339#). This may require, that you create a batch file for soffice on windows, unset PYTHONPATH in the system configuration or always start soffice from the shell with set PYTHONPATH= (windows) or unsetenv PYTHONPATH (unix tcsh shell).
    c:\Program Files\OpenOffice.org1.1\program> pkgchk hello_world.zip
    c:\Program Files\OpenOffice.org1.1\program> 
    
    On success no output is given by the tool. When you now start the office, there exists a new menu entry (see Tools/Addtional Components/Insert Hello World).

As you have seen, the core script lines are identical, but the ways to retrieve the office component context differ.

Examples

UNO Language binding

In the following you find the full description about how UNO features are mapped to the python language.

UNO Type mapping

IDL datatype representation in python
integer types (byte, short, unsigned short, long, unsigned long, hyper, unsigned hyper Python internally knows only the C datatypes long and long long as integer types. On most machines, a long is a 32 bit value while long long is a 64 bit value.
  • Values coming from UNO (for instance the return value of a UNO method)

    Values which have the type byte, short, unsigned short, long or unsigned long are converted to a python long value. Values which have the type hyper or unsigned hyper are converted to a python long long.

  • Values going to UNO (for instance the argument of a UNO method)

    If there is a concrete type in the idl method signature, the value is converted to the concrete type (in fact the invocation service does this work). If the method signature just has an any, every integer value is converted to the smallest data type, where the value fits into and send to the UNO object ( so 5 becomes a byte, 150 becomes a short, 0x1f023 becomes a long and values larger than 0xffffffff become a hyper.

boolean Python internally has a boolean data type, which is derived from the integer type ( see http://python.org/peps/pep-0285.html ). There exists the singletons True and False, which pyuno uses to distinguish between integers and boolean values.

As long as a boolean is specified in the interface method signature, you may also use numbers. In the following example, all calls are valid:
#idl signature void takeBool( [in] boolean bool )

unoObject.takeBool( 1 )       # valid, passing true (PyUNO runtime
                              # does the conversion
unoObject.takeBool( True) )   # valid, passing true
unoObject.takeBool( False )   # valid, passing false
However, when you want to explicitly pass a boolean, where only an any is specified, you must use True or False.
# idl signature void foo( [in] any value )

# implementation expects a boolean (which is separately documented
# e.g. in the service specification.
unoObject.foo( True ) # valid, pass a true
unoObject.foo( 1 )    # bad, just passing a 1, implementation will
                      # probably not be able to deal with it correctly.

Note: There also exists the uno.Bool class, which is deprecated since pyuno 0.9.2, but still supported. Don't use it anymore.

string In general, the string is mapped to the python unicode string. However, you may pass an 8 bit python string where a UNO string is expected, the bridge converts the 8 bit string to a unicode string using the system locale.
# idl signatore foo( [in] string value )
# both lines are valid
unoObject.foo( u'my foo string' )
unoObject.foo( 'my foo string' )
char A char is mapped to a uno.Char. It has a public unicode string member value with length 1 containing the unicode char.
# idl signature foo( [in] char c)
unoObject.foo( uno.Char( u'h' ) )  #valid
unoObject.foo( 'h' )               #wrong
enum A concrete enum value is represented by an instance of the class uno.Enum. It has two members, typeName is a string containing the name of the enum type and value contains the value of the enum.

You may create concrete enum values in two ways

  1. (suggested) by importing
    from enumname import enumvalue.

    E.g.

    from com.sun.star.uno.TypeClass import UNSIGNED_LONG
    .
    .
    .
    unoObject.setValue( UNSIGNED_LONG )
    if unoObject.getValue() == UNSIGNED_LONG:
       .
       .
    

  2. (in rare situations)
    import uno
         
    unoObject.setValue( uno.Enum( "com.sun.star.uno.TypeClass", "UNSIGNED_LONG") )
    if unoObject.getValue() == uno.Enum( "com.sun.star.uno.TypeClass", "UNSIGNED_LONG"):
       .
       .
       .
    
The first solution has the advantage, that a mispelled enum name already leads to a RuntimeException, when the python source file is imported.
type A type is mapped to a uno.Type. It has public members typeName (string) and typeClass (enum value of com.sun.star.uno.TypeClass). There exists a function uno.getTypeByName() to easily create a type instance, the functions raises a RuntimeException in case the type is unknown.

You may create concrete type values in two ways

  1. (suggested) by importing
    from module-where-type-lives-in import typeOfTypeName.

    E.g. to create XComponent's type, use

    from com.sun.star.lang import typeOfXComponent
    .
    .
    .
    unoObject.setType( typeOfXComponent )
    if unoObject.getType() == typeOfXComponent:
       .
       .
    

  2. (in rare situations, e.g. for types of simple values)
    import uno
         
    unoObject.setType( uno.getTypeByName( "com.sun.star.uno.XComponent" ) )
    if unoObject.getType() == uno.getTypeByName( "com.sun.star.uno.XComponent"):
       .
       .
       .
    
struct (and exception) For each UNO struct (or exception), a new python class is generated on the fly. It is guaranteed, that there is only one instance of the struct (or exception) class per python interpreter instance. The generated class does reflect the inheritance hierarchy of the conrete UNO type (e.g. important for exception handling, see below).

One can generate a struct class by using the import mechanism. An instance of a struct can then be instantiated by using the python constructor. The constructor supports zero arguments (members get default constructed), 1 argument which the same type (copy constructor), and n arguments, where n is the number of elements of the concrete struct. The struct supports the equality operator, two structs are equal, if they are of the same type and each member is equal.

Example:
from com.sun.star.beans import PropertyValue
from com.sun.star.uno import Exception,RuntimeException

propVal = PropertyValue()                 # Default constructor
propVal.Name = "foo"
propVal.Value = 2

if propVal == PropertyValue( "foo", 2 ):  # Memberwise constructor
   # true !
   pass

if propVal == PropertyValue( propVal ):   # Copy Constructor
   # true 


An instance of a UNO struct can be initially constructed with the function uno.createUnoStruct() and passing the name of the struct as the first parameter and optionial constructor arguments (see above for an example of possible ctors).

ATTENTION: In UNO, structs have value semantic, however the handling in python does not reflect this. When a struct gets passed as a parameter to a function, the values are passed to the callee. Later modification of the struct instance does not influence the callee anymore. However, simply assigning a struct to another local variable does not create a copy, but simply creates an alias to the original instance.
struct = uno.createUnoStruct( "com.sun.star.beans.PropertyValue" )

struct.Name = "foo"
struct2 = struct
struct2.Name = "python"           # modifies also struct, probably not desired !
unoObject.call( struct, struct2 ) # passes the same struct 2 times !

struct.Name = "doobidooo"         # even worse style. If the UNO object is implemented
                                  # in python, you possibly modify the callee's value.
				  # Don't do this !

sequence A sequence is in general mapped to a python tuple. A python list is not (!) accepted.
# idl signature XInterface createInstanceWithArguments(
#                        [in] string servicename,  [in] sequence <any> )
doc = smgr.createInstanceWithArguments( "foo.service", ("arg1",2))
Attention (since 0.9.2): The idl sequence<byte> is mapped to the class uno.ByteSequence . It has a string member named value, which holds the data of the byte sequence. As the bytesequence most often is a container for binary data, this class allows to handle binaries efficiently. This also embeds pyuno nicely into python, as python keeps binary data in strings. Example:
# idl signature writeBytes( [in] sequence%lt; byte > data )
#
out.writeBytes( uno.ByteSequence( "abc" ) )

# you could also write the following
begin = uno.ByteSequence( "ab" )
out.writeBytes( begin + "c" )

# but this does not work !
out.writeBytes( "abc" ) # ERROR, no implict conversion supported by the runtime !


# idl signature long readBytes( [out] sequence<byte> , [in] length )
len,seq = in.readBytes( dummy, 3 )

# the statements do the same thing 
print seq == "abc":
print seq == uno.ByteSequence( "abc" )
constants An UNO idl constant can be given by the following ways:
  1. Use the concrete value specified in the idl file
    A constant is its value and only its value. As modification of the constant values is incompatible, one may simply rely on the values.
  2. (suggested) Use the import mechanism to create variable with the constant name
    This solution is the most readable one.
  3. Use uno.getConstantByName()
    Might be useful from time to time. Function raises a RuntimeException in case the constant is unknown.
from com.sun.star.beans.PropertyConcept import ATTRIBUTES
.
.
.
# the following 3 lines are equivalent
unoObject.setConcept( ATTRIBUTES )
unoObject.setConcept( 4 )
unoObject.setConcept( uno.getConstantByName( "com.sun.star.beans.PropertyConcept.ATTRIBUTES" ) )
any In general, the python programmer does not come into touch with anys. At all places where anys appear in method signatures, the python programmer can simply pass a concrete value. Consequently, return values or out parameters also never contain a concrete any.

However, there are certain circumstances, where a python programmer may want to pass a concrete typed value to a callee (note, this is only possible for 'bridged' calls, you can't pass a typed any to another python uno object).

You can create a uno.Any() by passing the type (as typename or as uno.Type) and the value.
# constructs a uno.Any, that contains a byte
byteAny = uno.Any( "byte" , 5 )

# constructs a sequences of shorts
byteAny = uno.Any( "[]short", (4,5))

These anys can only be used in conjunction with the uno.invoke, which allows to invoke a method on an arbitrary UNO object with a typed any.
# the normal call
uno.setPropertyValue( "foo", (4,5))

# the uno.invoke call
uno.invoke( obj, "setPropertyValue" , ("foo",uno.Any( "[]short", (4,5))) )
When obj is a bridged object, the callee gets the sequence as a sequence<short>. When obj is a local python object, it gets simply the (4,5) as it would have got it with the normal call.

Implementing UNO objects

One may use python classes to implement UNO objects. Instances of a python class may then be passed as argument to UNO calls where anys or concrete interfaces are specified.

To be an UNO object, a python class MUST implement the com.sun.star.lang.XTypeProvider interface by implementing two methods getTypes() and getImplementationId(), which inform the python-UNO bridge, which concrete UNO interfaces the python class implements. The getTypes() function defines, which interfaces are implemented by the class.

To make this easier, there exists a unohelper.Base class, where a python UNO object should derive from. You can then implement a UNO interface simply by deriving from the wanted interfaces. The following example implements a com.sun.star.io.XOutputStream, which stores all data written into the stream within a ByteSequence. (Note that this is quite a poor implementation, which is just for demonstration purposes).
import unohelper
from com.sun.star.io import XOutputStream
class SequenceOutputStream( unohelper.Base, XOutputStream ):
      def __init__( self ):
          self.s = uno.ByteSequence("")
          self.closed = 0
          
      def closeOutput(self):
          self.closed = 1

      def writeBytes( self, seq ):
          self.s = self.s + seq

      def flush( self ):
          pass

      def getSequence( self ):
          return self.s
          
From the list of base classes given (here only XOutputStream), the unohelper.Base implementation correctly implements the XTypeProvider interface.

Implementing Python UNO components

There exists a loader for python components. It allows to create instances of python classes not just within the python process but in every arbitrary UNO process including OpenOffice.org. The python loader loads the python runtime on demand if it is not already loaded and executes python code within the root python interpreter.

If the reader is unfamilar with the component registration process, it should visit the OpenOffice.org developer manual for a comprehensive explanation.

The python loader currently supports the following protocols for incoming urls :
Protocol name Description
vnd.openoffice.pymodule The protocol dependend part is interpreted as a python module name, which is imported using the common python import mechanism (which uses the PYTHONPATH environment variable).

Example: vnd.openoffice.pymodule:MyPythonComponent
Using this url e.g. in XLoader.activate() will try to load a MyPythonComponent.py file from directories, which are listed within the PYTHONPATH environment/bootstrap variable. Note that you must not use the .py suffix here.

The given module is added to the sys.modules hash map.

file A mandatory absolute file url to a python component file. The file itself does not need to be contained within PYTHONPATH, but it may only import files, which are contained within PYTHONPATH. The module is not added to sys.modules.

Example: file:///path/to/MyPythonComponent.py

vnd.sun.star.expand The python loader supports the common macro expansion mechanisms as the Java or C++ loader does.

Example: vnd.sun.star.expand:$UNO_USER_PACKAGES_CACHE/MyPythonComponent.py

After the module has been imported, the python loader looks for a module-global variable with the name g_ImplementationHelper, which is expected to be an instance of unohelper.ImplementationHelper. The following sample code makes a uno component out of the above UNO object ( note that the component is not useful, because there is no UNO method to retrieve the tuple nor does a com.sun.star.io.OutputStream service specification exist, it's just here as an example).
import unohelper
from com.sun.star.io import XOutputStream

g_ImplementationHelper = unohelper.ImplementationHelper()

class TupleOutputStream( unohelper.Base, XOutputStream ):
      # The component must have a ctor with the component context as argument.
      def __init__( self, ctx ):
	  self.t = ()
	  self.closed = 0

      # idl void closeOutput();
      def closeOutput(self):
	  self.closed = 1

      # idl void writeBytes( [in] sequence seq );
      def writeBytes( self, seq ):
	  self.t = self.t + seq      # simply add the incoming tuple to the member

      # idl void flush();
      def flush( self ):
	  pass

      # convenience function to retrieve the tuple later (no UNO function, may
      # only be called from python )
      def getTuple( self ):
	  return self.t

# add the TupleOutputStream class to the implementation container,
# which the loader uses to register/instantiate the component.
g_ImplementationHelper.addImplementation( \
	TupleOutputStream,"org.openoffice.pyuno.PythonOutputStream",
	                    ("com.sun.star.io.OutputStream",),)
Lets assume, that this code is stored in a file named tuplestrm.py and the file exists somewhere within the PYTHONPATH variable, it can be registered to an OO1.1beta build with the following command :

regcomp -register -br types.rdb -br services.rdb -r services.rdb -c vnd.openoffice.pymodule:tuplestrm

You can of course also use the pkgchk tool as explained in the tutorial chapter with

pkgchk tuplestrm.py

, but note, that this command creates a copy of the file (when the script changes,it must be redeployed using the above command).

The component can be instantiated e.g. from OpenOffice Basic with
tupleStrm = createUnoService( "com.sun.star.io.OutputStream" )
tupleStrm.flush()

Out parameter handling

UNO out parameters are handled through the python multiple return value feature. For pure outparameters, a dummy None value should be used as a place holder. This is best explained with an example. Lets' assume we have the following IDL method spec
long foo( [in] long first, [inout] long second, [out] third )
A python UNO object implements such a method the following way :
class Dummy( XFoo ):
    def foo( self, first,second,third):
        # Note: the value of third is always None, but it must be there
	#       as a placeholder if more args would follow !
        return first,2*second,second + first	
then such a method would be called from python the following way
ret,second,third = unoObject.foo( 2, 5 , None )
print ret,second,third    # results into 2,10,7
. This also emphasizes, that out-parameters are quite close to multiple return values (though the semantic association of a inout parameter gets lost).

However, note that

Exception handling

The Python-UNO bridge uses the common Python exception handling mechanism. For every UNO exception, a concrete exception class is generated on the fly (see above type mapping table for an explanation how to do this).

Example for catching
from com.sun.star.uno  import RuntimeException
from com.sun.star.lang import IllegalArgumentException
from com.sun.star.connection import NoConnectException
try:
    uuresoler.resolve( "uno:socket,host=localhost,port=2002;urp;StarOffice.ComponentContext" )
except NoConnectException e:
    print "The OpenOffice process is not started or does not listen on the resource ("+e.Message+")"
except IllegalArgumentException e:
    print "The url is invalid ( "+ e.Message+ ")"
except RuntimeException e:
    print "An unknown error occured: " + e.Message
Example for throwing
from com.sun.star.io import IOException
class TupleOutputStream(XOutputStream,unohelper.Base):
      def writeBytes( self, seq ):
          if self.closed:
	     raise IOException( "Output stream already closed", self )
          self.t = self.t + seq

unohelper module

The unohelper.py module contains some sugar functions/classes, which are nice to use with pyuno, but not mandatory. This paragraph lists some of the unohelper.py features.
def systemPathToFileUrl( systemPath ) Returns a file-url for the given system path. Most of the OOo API functions expect a file-url, while the python runtime functions in general only work with system pathes. The function is implemented using the core C function osl_getFileUrlFromSystemPath().
def fileUrlToSystemPath( url ) Returns a system path (determined by the system, the python interpreter is running on). Most OOo function return a file-url, while most python runtime functions expect system pathes. The function is implemented by using the core osl_getSystemPathFromFileUrl() function.
def absolutize( path, relativeUrl ) Returns an absolute file url from a given, mandatory absolute, directory url and a relative file url, which may be absolute or relative (which includes e.g. ../ parts. The function is implemented by using the core osl_getAbsolutePathFromFileUrl() function.
def addComponentsToContext( toBeExtendedContext, contextRuntime, componentUrls, loaderName ) This functions adds a tuple of component urls to the toBeExtendedContext using the contextRuntime to instantiate the loader loaderName and some other services needed for this task. After completing the function, all services within these components can be instantiated as long as the toBeExtendedContext is not disposed. The changes are not made persistent.
def inspect( unoobject, file ) Dumps the typeinformation about the given UNO object into a file (in fact, file needs to be an instance of a class, that implements a write method). The typeinformation include implementation name, supported services, supported interfaces, supported methods and supported properties.

Dependencies

This chapter is most interesting for people who want to use the Python-UNO bridge independently from OpenOffice.org.

Unlike the Java or C++ UNO binding, the python UNO binding is not self contained. It requires the C++ UNO binding and additional scripting components. These additional components currently live in the shared libraries typeconverter.uno,invocation.uno,corereflection.uno,introspection.uno,invocadapt.uno, proxyfac.uno,pythonloader.uno (on windows typeconverter.uno.dll,...; unix typeconverter.uno.so,...).

Often, the components for setting up an interprocess connection are additonally required. These are uuresolver.uno,connector.uno,remotebridge.uno,bridgefac.uno shared libraries.

The path environment variables ( LD_LIBRARY_PATH on unix, PATH on windows) must point to a directory, where the core UNO libraries, the above listed components and the pyuno shared library is located. (On unix, there exists two files: libpyuno.so containing the code and a pyuno.so which is needed for importing a native python module). Additionally, the python module uno.py, unohelper.py and pythonloader.py must be located in a directory, which is listed in the PYTHONPATH environment variable.

Bootstrapping pyuno from the python executable

When the uno module gets first imported from an arbitrary python script, it must bootstrap a properly prepared UNO component context.
# bootstraps the uno component context
import uno

# retrieve the already bootstrapped component context
unoContext = uno.getComponentContext()
As the python programmer can't (and probably doesn't want to) give parameters during importing a module, the python-uno binding uses the pyuno[rc|.ini] file located beside the pyuno shared library to bootstrap the UNO context (see uno bootstrap variable concept). The bootstrap variables UNO_SERVICES must point to a registry file, where the above given components got registered to.

PYUNOLIBDIR is a special bootstrap variable, which contains the path to the currently used pyuno shared library. Example:
# The bootstrap variable PYUNOLIBDIR will be set by the pyuno runtime library
UNO_TYPES=$PYUNOLIBDIR/types.rdb
UNO_SERVICES=$PYUNOLIBDIR/pyuno_services.rdb
If the above preconditions are fulfilled, the script can simply be started with

$ python myscript.py

Sometimes it is preferable to mention the librarynames of the desired components directly within the script instead of preparing a registry (however note that the above mentioned bootstrap components always needs to be registered in a registry). This can be achieved by using the function unohelper.addComponentsToContext( toBeExtendedContext, contextRuntime, componentUrls, loaderName )

Example:
import uno
import unohelper

localContext = uno.getComponentContext()

unohelper.addComponentsToContext(
       localContext, localContext, ("streams.uno",),
       "com.sun.star.loader.SharedLibrary")

pipe = localContext.ServiceManager.createInstanceWithContext(
              "com.sun.star.io.Pipe", localContext )

pipe.writeBytes( uno.ByteSequence( "abc" ) )
ret,seq = pipe.readBytes( None, 3 )

Building from source

The Python-UNO Bridge consists of two source modules,
  1. external/python (or just python)
    This module builds the external python source. Put the python source tar file into the python/download directory, but make sure to rename it from Python-2.2.2.tgz to Python-2.2.2.tar.gz. (Note: Currently, you must use the Python-2.2.2 version !)

    Build and deliver this project.

  2. udk/pyuno (or just pyuno)
    The following table gives an overview about the pyuno project.
    prj              -- build details
    source/module    -- contains the core Python-UNO bridge and 
                        the module, that is loaded my the python executable.
    		    The result is a shared C++ library, to which the python loader
    		    links to.
    source/loader    -- contains the UNO loader for python components
    inc/pyuno        -- contains exported API for the python module. 
    unotypes         -- generates the types needed for the bridge
    test             -- contains test python scripts. In order to execture the tests,
                        simply type dmake test
    demo             -- contains demo python scripts and a makefile
                        , which creates a deployment file for the OO1.1beta build.
    zipcore          -- creates a zip file, which contains the whole python core. This
                        file is used during OpenOffice.org setup.
    
    Build this project. In order to run the tests, you need to get a HEAD version of the testtools project, which currently does not get built by default, build and deliver the testtools project. Afterwards run the tests in the pyuno/test directory using dmake runtest, you shouldn't get any errors there. Deliver this project.

Replacing the default python core with your system's python installation

OOo by default ships with the Python-2.2.2 core runtime. This is fine for most users, but some hackers may want to replace the runtime with either a newer python runtime (e.g. Python-2.3) or with the python system's installation, which e.g. may contain more optional packages, which you also want to use in python.

This shall not be a full explanation, on how you can exchange the python runtime. Also note, that this has not been tested. However, here is a list of hints, which should help you doing this.

Support for the new scripting framework

There is now a uno-package available, which allows the support of the new scripting framework for OpenOffice.org 2.0. It is included in the above download . It allows to execute python scripts in the new framework. This paragraph shall just explain how to install and use the python part of the new framework, it shall not explain the framework itself.

Installation

  1. Install an OpenOffice.org 1.1rc or newer with PyUNO activated ( see above ).
  2. Install the scripting framework (tested with version 0.3)
  3. Copy the file scriptrt4python-0.1.0.zip into <office-install>/user/uno_packages
  4. run pkgchk in <office-install>/program
  5. Deploy the sample script stored in the samples/hello-framework-python.sxp parcel into the office (e.g. with the scripting-framework CommandLineTools, for example

      java CommandLineTools -d hello-framework-python.sxp  \
               /usr/local/joerg/OpenOffice.org1.1/user/Scripts
    
    ).

Deinstallation

  1. Uninstall the hello-framework-python.sxp
  2. delete the file <office-install>/user/uno_packages/scriptrt4python-0.1.0.zip
  3. run pkchk

Writing your own scripts

You can write scripts simply by defining python functions. You can have multiple functions within one file, but the file must be selfcontained meaning that it may only reference the core python library but not any self written scripting files (may be a future extension).

Below you can find the sample script, which just pastes a 'Hello Scriptingframework' into the current document. The following script is stored as HelloFramework.py.
# Sample python script for the scripting framework

# scriptCtx supports getDocument(), getComponentContext(), getDesktop()
def Hello( scriptCtx ):
    print "entering HelloFramework.Hello"
    
    model = scriptCtx.getDocument()

    # access the document's text property
    text = model.Text

    # create a cursor
    cursor = text.createTextCursor()

    # insert the text into the document
    text.insertString( cursor, "Hello Scriptingframework", 0 )

    return None
Beside the script file you must provide a parcel-descriptor.xml file which describes all functions, that shall be callable fromt the office. Here is the sample file for the above script.
<?xml version="1.0" encoding="UTF-8"?>
<parcel language="Python" xmlns:parcel="scripting.dtd">
    <script language="Python">
        <locale lang="en">
            <displayname value="HelloFramework.Hello"/>
            <description>
                Prints a 'Hello Framework' into the current document
            </description>
        </locale>
        <functionname value="HelloFramework.Hello"/>
        <logicalname value="HelloFramework.Hello"/>
    </script>
</parcel>
Note, that the each reference to the script must be written as <source-file-name-without-.py>.<function-name>.

The function gets called with a XScriptContext implementation as the first parameter. It provides the UNO component context (getComponentContext()), the desktop (getDesktop()) and the current document model (getDocument()) (if any).

Zip these two files as a .sxp file and deploy it into the office using the scripting frameworks commandlinetool. When you restart the office, you should be able to select the new script in the scriptinframework's dialogs.

Things to keep in mind:

External references

Python homepage http://www.python.org
The OpenOffice.org component model http://udk.openoffice.org
OpenOffice.org developer manual http://api.openoffice.org/DevelopersGuide/DevelopersGuide.html

Frequently Asked Questions

  1. Why do I get a 'bus error' when starting the hello-world-script on solaris ?

    There seems to be a corrupted version of the libpyuno.so in the OpenOffice.org1.1.0 installation set. The reason is not yet clear, might be either a bug in pyuno code or a build error. Please download libpyuno.so.gz to patch OOo1.1.0 version (do not apply this patch on any other version than OOo1.1.0 solaris sparc!).
  2. Why do I get a 'SystemError: pyuno runtime is not initialized, ...' when starting the script ?

  3. Why do I get a 'SystemError: _PyImport_FixupExtension: module pyuno not loaded' when starting the script ?

    This in general happens, when you still start the system's python installation. OpenOffice.org ships a python installation (because python and the office must have been built with the identical compiler version). Please check this with 'which python'. Simply address OpenOffice.org's python absolutely, e.g. use /usr/local/OpenOffice.org1.1/program/python myscript.py.
  4. Why do I get a "error: python-loader:'No module named pythonloader'" when running pkgchk with a python component ?

    Make sure to unset PYTHONPATH and PYTHONHOME (which you may have set, because you have an own python installed on your system) environment variables before running soffice AND pkgchk. This is a workaround, it is currently thought about a better solution.
  5. Why do I get an error message 'msvcr70.dll or python22.dll not found' when starting python ?
    (or Why do I get an 'error while loading shared libraries: libstdc++.so.x' ? )

    You probably try to start python from e.g. c:\program files\OpenOffice.org1.1\program\python-runtime\bin\python.exe, but you have to use c:\program files\OpenOffice.org1.1\program\python.bat.
  6. Why do I get 'PYTHONPATH=... is not an identifier' when starting python ?

    This is a bug in the python script which occurs with older bash shell versions. Simply use a text editor to change the following lines in the OOo-install/program/python.sh script
    export PYTHONPATH="$sd_prog":"$sd_prog/python-core/lib":"$sd_prog/python-core/lib/lib-dynload":"$PYTHONPATH"
    export PYTHONHOME="$sd_prog"/python-core
    
    to
    PYTHONPATH="$sd_prog":"$sd_prog/python-core/lib":"$sd_prog/python-core/lib/lib-dynload":"$PYTHONPATH"
    export PYTHONPATH
    PYTHONHOME="$sd_prog"/python-core
    export PYTHONHOME
    
    . This bug is fixed with OOo 1.1.1.
  7. I already have python installed on my system, why does the office ship another python ?

    Python itself is shipped with OpenOffice.org, because
  8. Can I use system's python installation ?

    In general this is possible on unix like systems, but needs a lot of work. Have a look at this paragraph to understand the basic ideas. On windows, this is only possible, when you relink pyuno itself, which means that you need to use the OOo build environment to do this.
  9. Why crashes the office with a self written UNO component, while the sample UNO component runs fine ?

    There is a known bug in the office, see #i13377#, which was not fixed for OpenOffice.org1.1. The office in general crashes, when the python script lead to an unhandled exception (e.g. attribute error etc.).

    You may try to workaround this bug by adding a try: except: level in your trigger() implementation, which dumps an error message to stdout/stderr, but sadly this will not help in all cases (e.g. compilation failure for some reason :o( ).

    Of course, there may be other reasons for a crash, you will only know, when you try to retrieve a native callstack (e.g. using the gdb).

  10. Why don't work Python's xml parser (expat) and the zip module for me ?

    These libraries don't yet get built for OOo1.1. This will change for OOo2.0. Alternatively you may use OpenOffice.org's xml parser service (see service com.sun.star.xml.sax.Parser) or the zip content provider (see http://ucb.openoffice.org).
  11. Why do not work socket and sre module in OOo1.1. python distribution on windows ?

    This is a known bug on windows in the OOo1.1 build. This should be fixed for OOo1.1.1 (see issue 21281 ). It should work for the other platforms. You can workaround this by downloading the officical windows python distribution (see http://www.python.org) and replacing the appropriate .pyd files in the OOo's python installation.
  12. The samples are running fine, but how do I get more information about the API ?

    The semantics of the OpenOffice API is a very complex topic, which can't be discussed in this python document. Try to gather information from other resources, especially from the developer manual (see below).
  13. Most examples in the devguide are in Java. How do I translate them to python code ?

    Most sample code you find there is written in Java. It is not so difficult to translate java code to python, when you know the following differences:

    In python you don't need queryInterface. E.g. Java code like
                oInterface = (XInterface) oMSF.createInstance(
    	                         "com.sun.star.frame.Desktop" );
                oCLoader = ( XComponentLoader ) UnoRuntime.queryInterface(
    	                         XComponentLoader.class, oInterface );
    	    PropertyValue [] szEmptyArgs = new PropertyValue [0];
    	    aDoc = oCLoader.loadComponentFromURL(
                         "private:factory/swriter" , "_blank", 0, szEmptyArgs );
    
    becomes in python simply
                oCLoader = oMSF.createInstance( "com.sun.star.frame.Desktop" )
    	    aDoc = oCLoader.loadComponentFromURL(
    	                 "private:factory/swriter", "_blank", 0, () )
    
    . You don't need this intermediate oInterface variable anymore. So, here python code simplifies a lot, with a little training, one shouldn't have too many problems to translate java to python code.

  14. Why can't I call the print method ?

    In python, 'print' is a very basic method for every object, which cannot be overriden so easily. E.g. the below code does not work.
         doc = desktop.loadComponentFromURL(infileurl, "_blank", 0, ())
         doc.storeAsURL(outfileurl, ())
         doc.print( () )
    
    You can workaround the problem by using the uno.invoke() function like below :
         uno.invoke( doc , "print" , ( (), ) )
    
  15. Why can't I do a replace on the 'NumberingRules' object ?

    There are some places, where the loss in type safety leads to difficulties, as e.g. bug http://www.openoffice.org/issues/show_bug.cgi?id=12504. The problem here is, that the C++ implementation within the office expects a sequence< PropertyValue >, while the PyUNO runtime converts it to a sequence< any>, where each any contains a PropertyValue. In my eyes, this is a bug within the C++ code. However, there has been added a workaround for pyuno, which the scripter can use. See the below sample:
    import uno
    import unohelper
    
    localContext = uno.getComponentContext()
    
    resolver = localContext.ServiceManager.createInstanceWithContext(
        "com.sun.star.bridge.UnoUrlResolver", localContext)
    ctx = resolver.resolve(
        "uno:socket,host=localhost,port=2002;urp;StarOffice.ComponentContext")
    smgr= ctx.ServiceManager
    desktop = smgr.createInstanceWithContext("com.sun.star.frame.Desktop",ctx)
    doc = desktop.loadComponentFromURL("private:factory/swriter", "_blank", 0, ())
                
    style = doc.createInstance("com.sun.star.style.NumberingStyle")
    family = doc.getStyleFamilies().getByName('NumberingStyles')
    family.insertByName('List test', style)
    
    rule = style.getPropertyValue('NumberingRules')
    level = rule.getByIndex(0)
    
    # the normal call would have been:
    # rule.replaceByIndex( 0, level )
    # but this will end up in a excpetion
    # magic to pass the exact type to the callee
    uno.invoke( rule , "replaceByIndex", (0, uno.Any("[]com.sun.star.beans.PropertyValue",level)) )
    

    This is the only place, where the uno.Any is used. Using the uno.Any in normal calls will lead to RuntimeExceptions. A python uno object implementation will never receive an instance of uno.Any() as a incoming parameter, instead always the value within the is passed.

    This solution looks really ugly, but it allows you to continue, where you otherwise can only give up or switch to another implementation language.

  16. How can I activate encoding iso8859-1 for OpenOffice.org's python installation ?

    In the <openoffice-install>/program/python-core/lib/site.py file, you can replace the line
    encoding = "ascii" # Default value set by _PyUnicode_Init()
    
    with
    encoding = "is8859-1" # Default value set by _PyUnicode_Init()
    
    (or any other encoding you wish). However, note that this is a per installation configuration. It would be better to do the necessary conversions explicitly in the code.

Authors

The UNO python bridge was initially created by Ralph Thomas and is now maintained by Joerg Budischewski. Christian Zagrodnick sent in some very useful patches.

Please use the dev@udk.openoffice.org mailing list for further questions.