Python IDE Basics

From The Document Foundation Wiki
Jump to: navigation, search


This page presents the steps enabling to develop LibreOffice Python macros within an Integrated Development Environment (IDE). Configuration or preferences setup are presented for a limited set of free and open source (FOSS) IDEs. Direct editing and testing of Python macros within LibreOffice requires solely ::APSO:: extension. In which case Starting, Connecting & Stopping chapter content can be skipped.

( ... Documents, Configuration, Object Inspection ... )

Useful LibreOffice Packs

For those wishing to explore cross-compatibility:

IDE Project Setup

Each IDE requires a different configuration or setup.

What's in a Macro

A LibreOffice Python macro may look like:

import uno
def my_1st_macro():
    # Won't run directly in Anaconda, Geany, KDevelop, PyCharm or else
    doc = XSCRIPTCONTEXT.getDocument()
    doc.getText().setString("Hello World!")

g_exportedScripts = my_1st_macro,

Executing the above requires to bridge together the IDE and LibreOffice. Once done UNO objects become accessible.

Up to five steps can be necessary to achieve this:

  1. Start LibreOffice as a service,
  2. Connect to a service,
  3. create a XSCRIPTCONTEXT Adaptor,
  4. Run the macro,
  5. Stop LibreOffice as a service

While examples available on the internet do not resort to all these steps, their study exhibits coding guidelines that the proposed module is borrowing from in order to innocuously integrate in IDEs.

Start, Connect, Adapt, Run & Stop

Using a LibreOffice Python macro library may resemble this:

import uno
def my_1st_macro(): pass  # Your code goes here

g_exportedScripts = my_1st_macro,  # *Office public macros

if __name__ == '__main__':
    ''' IDE runnable code: *Office as a service '''
    from IDE_utils import Runner, XSCRIPTCONTEXT
    with Runner() as jesse_owens:  # Start/Stop, Connect/Adapt
        my_1st_macro  # Run

Below table is listing a few public (Libre/Open)Office Python macros examples. Their usage of the Start, Connect, Adapt, Run, Stop steps is discussed. IDE_utils design choices sometimes originate from them.

Example codes
Description Start Connect Adapt Run


a. LibreOffice and OpenOffice module x x
b. A bridge for IDE with XSCRIPTCONTEXT revisité, Feb. 2017 (in french) x x x
c. Interface-oriented programming in OpenOffice / LibreOffice: automate your office tasks with Python macros, Sep. 2013 x x
d. LibreOffice macro execution with Pycharm, Jan. 2014 (in japanese) x x x
e. - Starting, Stopping and Connecting to OpenOffice with Python, Dec. 2008 x x x x
f. Python UNO-bridge, Oct. 2008 x

Executing a Python macro from an IDE requires to run Office as a service, that is to detach a LibreOffice process that's concurrent to the Python macro. Stopping this *Office parallel process, after the macro has executed, frees significant CPU cycles as well as it saves memory consumption.

Starting Office

officehelper and ooutils modules respectively propose bootstrap() and startup() routines to launch the Office suite from Python. Numerous startup parameters exist in order to run LibreOffice as a service. They can be searched for in "Starting LibreOffice Software with parameters" help page as well as online. Two different types of service exist:

  • Pipe connections
  • Socket connections

Modules comparison:

  • The officehelper multi-platform module manages solely pipe connections, which may fit most testing needs. It hardwires nologo and nodefault startup parameters,
  • The Unix-based ooutils module provides socket connection with parametrized startup and it exhibits desktop pooling. It hardcodes OpenOffice-driven startup parameters. Worth nothing is a convenient termination of *Office services.
  • IDE_utils multi-platform module supports pipe or socket connections to *Office. On-demand startup options are settable according to specific test cases, such is documented in "Getting Started - Customized Service" IDE_utils guide. A Runner() context manager class is responsible for starting LibreOffice or OpenOffice.

Stopping Office

officehelper and ooutils both resort to os module spawn() facility. IDE_utils module launches services using subprocess standard library Popen() function. Unfortunately initialized *Office services process trees differ across platforms and products, making it difficult to interrupt services homogeneously.

Process tree examples:

  • LibreOffice / MacOS: ...
  • Apache OpenOffice / Linux: oosplash detaches soffice
  • LibreOffice / Windows: soffice.exe detaches soffice.bin

As a consequence such detached processes are not easily stoppable using kill() and terminate() functions from os or subprocess standard libraries. Luckily one can terminate *Office using Desktop API implementation, as demonstrated by ooutils.

By default IDE_utils performs process background termination using *Office API, in combination with an implementation of XScriptContext API interface:

def _terminate_desktops():
    for ctx in ScriptContext.pool.values():  # {key: ctx} pairs

Moreover, remote socket connected services are stopped in the same manner.

Adapting with ScriptContext()

"XSCRIPTCONTEXT revisité" Office.Bridge emulates XSCRIPTCONTEXT PyUNO built-in.

"Macro execution with PyCharm" unopy module uses Interface Injection, a kind of Dependency Injection design pattern illustrating Inversion of Control. Its if code block gets processed from any IDE while being ignored from LibreOffice or OpenOffice. unopy module can be copied into <OFFICE>\program directory, while being not required at build time. A neat XSCRIPTCONTEXT adaptor with zero impact on macro code!

IDE_utils and unopy modules both publish a ScriptContext() class implementing XScriptContext API interface:

Connecting to Office

"XSCRIPTCONTEXT revisité" Office.Bridge module proposes both pipe and socket hardcoded connections. "Macro execution with PyCharm" ...

Connection Decoration

Extending IDE_utils

module skeleton

Connection Pooling with ScriptContext()

object scope vs. module scope



@atexit decoration

Recommended Use

import uno
def my_1st_macro(): pass  # Your code goes here

g_exportedScripts = my_1st_macro,  # Published macros

if __name__ == '__main__':
    from IDE_utils import Runner, XSCRIPTCONTEXT
    with Runner as jesse_owens:  # Start/Stop, Connect/Adapt
        my_1st_macro  # Run

The example above should be your preferred use for IDE_utils. However starting and stopping a service may not fit all situations, thus those steps are optional. That same code allows the customization of *Office services running conditions. "Getting Started" user guide provides detailed description of the 3 different ways to use IDE_utils:

  • Resorting to (Libre/Open)Office default Python bootstrap() mechanism,
  • Letting Runner() and ScriptContext() objects take responsibility for start, connect, adapt, run and stop steps,
  • Deciding when to perform start, connect, adapt, run and stop steps.

Examples Highlights

a. Module

The module, included in LibreOffice and OpenOffice, starts and connects to a LibreOffice instance:

""" Start and connect to (Libre/Open)Office """
import officehelper
ctx = officehelper.bootstrap()

Module help is available from Python interpreter:

>>> import officehelper
>>> help(officehelper)
Help on module officehelper:

bootstrap() help states: The soffice process is started opening a named pipe of random name, then the local context is used to access the pipe. This function directly returns the remote component context, from whereon you can get the ServiceManager by calling getServiceManager() on the returned object.

Starting a service and connecting to it is achieved using Uno-Urls that bridge a Python macro and a running Office instance.

Two different types of connections can be established with LibreOffice:

  • Pipe connections
  • Socket connections

Numerous startup parameters exist in order to run LibreOffice as a service. They can be searched for "Starting LibreOffice Software with parameters" help page as well as online. officehelper module uses two of them: nologo and nodefault.

Notes: The officehelper multi-platform module manages solely pipe connections which may fit most testing needs. Service startup --options are hardwired. Neither stopping means, nor XSCRIPTCONTEXT enablement are present. Observe that extra macro code insertion is necessary.

b. XScriptContext reloaded

""" Please comment this line when running within (Libre/Open)Office """
from Office.Bridge import XSCRIPTCONTEXT

Office.Bridge indicates: The XSCRIPTCONTEXT object facilitates connections to running instances of (Libre/Open)Office. It connects to a piped instance of (Libre/Open)Office named 'LibreOffice'. It connects to a localhost instance of (Libre/Open)Office using port=yyyy (yyyy=current year). It can connect to any variation of pipe name or port# used by a started (Libre/Open)Office instance.

Notes: The Office.Bridge module proposes both pipe and socket hardcoded connections, and introduces a form of connection pooling. Starting and stopping mechanisms are missing. XSCRIPTCONTEXT built-in is supported while code insertion is necessary. Its Python code is unnecessarily verbose.

c. Interface-oriented programming..

import socket  # only needed on win32-OOo3.0.0
import uno

# get the uno component context from the PyUNO runtime
localContext = uno.getComponentContext()

# create the UnoUrlResolver
resolver = localContext.ServiceManager.createInstanceWithContext(
                                "", 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( "",ctx)

# access the current writer document
model = desktop.getCurrentComponent()

Notes: Interface-oriented programming.. solely introduces a hardcoded socket connection. Inserting a few lines is necessary. Despite these facts, that post is a valuable introduction to Python macros.

d. Macro execution with PyCharm

A Python macro may be written and ran as:

def HelloWorld_Writer():
    doc = XSCRIPTCONTEXT.getDocument()
    doc.getText().setString("Hello World!")

if __name__ == "__main__":
    import unopy
    XSCRIPTCONTEXT = unopy.connect()

Worth mentioning is the use if XSCRIPTCONTEXT Interface Injection, a kind of Dependency Injection design pattern illustrating Inversion of Control. A notable advantage is the if code block that gets processed from any IDE while being ignored from LibreOffice or OpenOffice. module can be copied into <OFFICE>\program directory, and is not required at build time. All in all it's a concise and efficient Adaptor implementation.

The complete module code can be obtained from japanese forums or GitHub:

Notes: The unopy module hardcodes a classical socket connection. Service start/stop are not coded. However its XSCRIPTCONTEXT adaptor is neat, and its impact on macro code is null, zero!

e. Starting, Stopping while Connecting..

import ooutils
oor = ooutils.OORunner()
desktop = oor.connect()
# Do something with the "desktop"

Get Starting, Stopping and Connecting complete code from Linux Journal. Observe OpenOffice startup options presented as list and key-values pairs.

def startup(self):
    Start a headless instance of OpenOffice.
    args = [OPENOFFICE_BIN,
            '-accept=socket,host=localhost,port=%d;urp;StarOffice.ServiceManager' % self.port,
    def _shutdown_desktops():
        """ Shutdown all OpenOffice desktops that were started by the program. """
        for port, desktop in _started_desktops.items():
                if desktop:
            except Exception, e:

Notes: The above link exhibits desktop connection pooling while providing a convenient parametrized startup and shutdown of the service. Its use of desktop.terminate() must be noted. This unix-based OpenOffice example, lacks the preceeding XSCRIPTCONTEXT adaptor, and requires extra code insertion.

f. Python UNO-bridge

Notes: Python UNO-bridge demonstrates a common socket connection. Its PyUNO architecture and Python extension coding documentation is worth reading. However start, adapt, run, and stop facilities are absent.

Coding Guidelines

Requirements for Python macro enablement in IDEs may summarize as:

  • start, connect, adapt, run and stop steps to be optional
  • Support multiple platforms i.e. essentially Linux, MacOS and Windows
  • on-demand startup --options
  • Permit pipe and/or socket connections
  • decoupled coding using injection
  • Provide Service pooling, context pooling
  • and KISS

IDE_utils module proposal: A Runner() context manager class is responsible for starting and stopping soffice instances. An optional JSON configuration file contains service-options pairs holding the services to start and their running conditions. A connect() function bridges the actual IDE and LibreOffice instances. A ScriptContext() object is injected as XSCRIPTCONTEXT built-in. start() and stop() functions are coding facilities that wrap-up Runner() features.

#!  #
import officehelper
RUNNERS = 'Runners.json'

class Runner(json=None): pass
class ScriptContext(): pass

_ctx = officehelper.bootstrap()
XSCRIPTCONTEXT = ScriptContext(_ctx)

def connect(host='localhost', port=2002, pipe=None): pass
def start(): pass
def stop(): pass

Thus a LibreOffice Python macro library can ressemble these examples:

import uno
def macro_1(): pass  # Your code goes here
def macro_2(): pass  # Your code goes here

g_exportedScripts = macro_1, macro_2  # *Office public macros

if __name__ == "__main__":
    ''' LibreOffice as a Service IDE runnable code''' 
    import IDE_utils as geany
    with geany.Runner() as jesse_owens:  # Start/Stop
        XSCRIPTCONTEXT = geany.XSCRIPTCONTEXT  # Connect/Adapt
        macro_2()  # Run

Example 1: As Runners.json does not exist jesse_owens starts nothing. officehelper opens a named pipe of random name, XSCRIPTCONTEXT injection occurs, macro_2 gets executed and LibreOffice running pipe gets stopped as jesse_owens context manager triggers it.

if __name__ == "__main__":
    import IDE_utils as pycharm 
    with pycharm.Runner(json={pgm: [accept, *options]}) as carl_lewis:  # Start/Stop
        ctx = pycharm.connect(pipe='LibreOffice')
        XSCRIPTCONTEXT = pycharm.ScriptContext(ctx)  # Adapt
        macro_1()  # Run

Example 2: As carl_lewis explores JSON pgm-options dictionary pairs, LibreOffice instances start. A hardwired 'LibreOffice' pipe connection is requested, macro_1 executes and carl_lewis terminates all started instances.
Note: A default injection is performed, in the background, with last pgm-options pair, and overrides officehelper bootstraping.

if __name__ == "__main__":
    import IDE_utils as usain_bolt  # Runner()
    ctx = usain_bolt.connect()
    XSCRIPTCONTEXT = usain_bolt.ScriptContext(ctx)  # Adapt
    pass  # Your code goes here

Example 3: usain_bolt starts all services found in optional Runners.json input file, attempts a common socket connection on port 2002, injects an implementation of XSCRIPTCONTEXT built-in. No macro code is called and usain_bolt finally stops all possible (Libre|Open)Office instances.

IDE_utils source code can be obtained from GitLab. IDE_utils module design decisions are drafted below and can be skipped. However IDE_utils Limitations lecture is recommended.

ScriptContext class

ScriptContext objects require an established bridge in order to implement XSCRIPTCONTEXT builtin interface:

  • getComponentContext(), getDestop(), getDocument()
  • getInvocationContext() is not defined
class ScriptContext(XScriptContext):
    def __init__(self, ctx):
        self.ctx = ctx
    def getComponentContext(self):
        return self.ctx
    def getDesktop(self):
        return self.ctx.getServiceManager().createInstanceWithContext("", self.ctx)
    def getDocument(self):
        return self.getDesktop().getCurrentComponent()

connect function

Above internet examples demonstrate connection time-out and connection exception handling in various ways.
a. officehelper module performs 20 attempts every half-second before it throws a BootstrapException.
d. Starting, Stopping... connects every second up to six times and raises Exception afterwards.
Observe that NoConnectException must be trapped when connection to LibreOffice.
The IDE_utils connect() function is delegating time-out and exception handling activities to a Python @retry decorator that's responsible for such aspects. Its delays, exception and report arguments may be overwritten using IDE_utils module data variables e.g. RETRY_DELAYS.
All achieved connections aggregate in ScriptContext.pool dictionary according to connect() parameters.
(Libre|Open)Office instances termination are performable out of this pool.

from __future__ import print_function

RETRY_DELAYS = (0, 1, 5, 30, 180, 600, 3600)
RETRY_EXCEPTIONS = NoConnectException

def retry(delays=(0, 1, 5, 30, 180, 600, 3600),
          report=lambda *args: None):

def connect(host='localhost', port=2002, pipe=None):
           report=RETRY_REPORT)  # Python3!
    def resolve():
        return resolver.resolve(uno_url)
    ctx = resolve()
    if pipe:
        ScriptContext.pool[pipe] = ctx
        ScriptContext.pool[(host, port)] = ctx
    return ctx

ScriptContext.pool = {}  # Can be fed by Runner processes too !

Services specification

Services to start and their running conditions are precised within JSON-like dictionary pairs. The expected syntax is {pgm: [accept, *options]} where minimum inputs are the soffice path plus --accept option. Other options appear according to particular use case expectations. LibreOffice and OpenOffice options specification syntax are different.
Single or simultaneous services can be managed.

WIN_libO_x64 = \
    {'C:\Program Files\LibreOffice 5\program\soffice.exe':
WIN_OOo_x32 = \
NIX_aOO = \

pgms = {**WIN_libO_x64, **NIX_aOO, **WIN_OOo_x32}  # Python 3!

with Runner(soffice=WIN_libO_x64) as jesse_owens: pass
# LibreOffice single named pipe.
with Runner(soffice=pgms) as carl_lewis: pass
# 3 different *Office versions

Alternatively services can be specified inside the Runners.json file. A different JSON file name can be used, providing IDE_utils.RUNNERS data variable is overwritten. _create_config() function exhibits service specification facilities for JSON files.

Runner class

Runner context manager class makes uses of __enter__ and __exit__ facilities to implement initialisation and termination of *Office services. It starts, stops zero-to-many (Libre|Open)Office processes according to an optional JSON configuration file containing {pgm: [accept, *options]} key-values service pairs.
Besides Runner objects establish service connections for later use.

import IDE_utils as ide

# ex.1
with ide.Runner() as jesse_owens:  # Starts/stops 'soffice' instances
    pass # Your code goes here

# ex.2
task = {'D:\Portable\App\openoffice\program\soffice.exe':
         ]  # OpenOffice - Foreground - visible instance
with ide.Runner(soffice=task) as carl_lewis:  # Portable OpenOffice Runner
    pass # Your code goes here

All started services get pooled in obj.processes to be later terminated, while prepared successful connections add to ScriptContext.pool with uno-url keys.

Help - ...
To be or not to Be UI ( Headless, invisible, ... )

Improvement Tracks, Limitations

Managing Documents

Opening a Document

Creating a Document

Saving a Document

Closing a Document

A General Purpose Converter

Bug Detection & Reporting


Examining LibreOffice

Configuration Properties

Path Settings

Getting & Setting DocumentProperties

Reporting OS File Properties

Setting Document Properties

Inspecting a Document for API Information

MRI, xRay & ObjectInspector


... catching document(s) events, catching URP bridge events...

import uno, msgbox
def listening():
    pass  # Your code goes here

g_exportedScripts = listening,
import msgbox