pythonlibreofficeopenoffice.orgunopyuno

Connecting to LO from python IDE with PyUNO for debugging


I am trying to set up an comfortable environment to develop some python scripts for LibreOffice Calc.

Before I tried to edit .py files directly from "/home/user/.config/libreoffice/4/user/Scripts/python/". This would not let me debug the script, because if I start it in debug mode, most of the components, like XSCRIPTCONTEXT would not be found.

I decided to connect to LO directly via uno from my venv elsewhere.

I start my virtual environment with python -m venv --system-site-packages venv and activate it

Now which python gives ../somefolder/venv/bin/python and pip list shows pyuno (version 0.1.1) and unoserver (version 1.3).

EDIT: I installed them with pip from virtual environment. I just realized that pyuno in pip-repo is a different thing entirely (https://pypi.org/project/pyuno/) and I uninstalled it and its dependencies. So only unoserver is installed inside venv and uno-path is: /usr/lib/libreoffice/program/uno.py

Then I start unoserver with unoserver &. This created a soffice process listening on localhost:2002, which I verified with netstat -anpe | grep "2002".

Then I open a calc document. As mentioned here: https://ask.libreoffice.org/t/why-does-desktop-getcurrentcomponent-return-none-in-pyuno/59902 you should do it before running the script, lets say MySpreadsheet.ods and try tu run the following script (adapted from the link):

import uno
# import time

context = uno.getComponentContext()
resolver = context.ServiceManager.createInstanceWithContext("com.sun.star.bridge.UnoUrlResolver", context)
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.getCurrentComponent()
print(doc.Title)

Upon variable inspection I can see that, all the variables are created. I do not know what to look for in all of them to verify the process, but when desktop = ... line is executed, that desktop object has all of the field None:

ComponentWindow: None
ContainerWindow: None
CurrentComponent: None
etc

which leads to the fact that desktop.getCurrentComponent() evaluates to None as well.

EDIT: When executing code I printed the created object sequence and it returns this:

context:

pyuno object (com.sun.star.uno.XComponentContext)0x558e2a7a1b20{, supportedInterfaces={com.sun.star.uno.XComponentContext,com.sun.star.container.XNameContainer,com.sun.star.lang.XTypeProvider,com.sun.star.uno.XWeak,com.sun.star.lang.XComponent}}

resolver

pyuno object (com.sun.star.uno.XInterface)0x558e2a941c60{implementationName=com.sun.star.comp.bridge.UnoUrlResolver, supportedServices={com.sun.star.bridge.UnoUrlResolver}, supportedInterfaces={com.sun.star.lang.XServiceInfo,com.sun.star.bridge.XUnoUrlResolver,com.sun.star.lang.XTypeProvider,com.sun.star.uno.XWeak}}

ctx

pyuno object (com.sun.star.uno.XInterface)0x7f1d88002b58{, supportedInterfaces={com.sun.star.uno.XComponentContext,com.sun.star.container.XNameContainer,com.sun.star.lang.XTypeProvider,com.sun.star.uno.XWeak,com.sun.star.lang.XComponent}}

smgr

pyuno object (com.sun.star.lang.XMultiComponentFactory)0x7f1d88002e78{implementationName=com.sun.star.comp.cppuhelper.bootstrap.ServiceManager, supportedServices={com.sun.star.lang.MultiServiceFactory,com.sun.star.lang.ServiceManager}, supportedInterfaces={com.sun.star.lang.XServiceInfo,com.sun.star.lang.XMultiServiceFactory,com.sun.star.lang.XMultiComponentFactory,com.sun.star.container.XSet,com.sun.star.container.XContentEnumerationAccess,com.sun.star.beans.XPropertySet,com.sun.star.beans.XPropertySetInfo,com.sun.star.lang.XEventListener,com.sun.star.lang.XInitialization,com.sun.star.lang.XTypeProvider,com.sun.star.uno.XWeak,com.sun.star.lang.XComponent}}

This answer to another post: https://stackoverflow.com/a/19158549/15452958 suggest to set PYTHONPATH to /usr/lib/libreoffice/program. Indeed pyuno.so is available in that folder and during debugging I can verify that uno is loaded from /usr/lib/libreoffice/program/uno.py.

Accepted answer here: Why does desktop.getCurrentComponent() return None in PyUNO? shows that I should wait until desktop becomes online. I tried and it did not work. I let it run like this for 5 minutes or so and doc was still None.

EDIT2: The output of netstat -anpe | grep "2002" now reads additional two lines for established connection.

tcp        0      0 127.0.0.1:2002          0.0.0.0:*               LISTEN      1000       13310724   213378/soffice.bin  
tcp        0      0 127.0.0.1:51848         127.0.0.1:2002          ESTABLISHED 1000       818657597  292648/python       
tcp        0      0 127.0.0.1:2002          127.0.0.1:51848         ESTABLISHED 1000       815824701  213378/soffice.bin 

In the documentation of older unoconv this resource is mentioned: [Tutorial] Import uno module to a different Python install http://user.services.openoffice.org/en/forum/viewtopic.php?f=45&t=36370&p=166783

As far as I understand this is irrelevant for this case as libreoffice uses system python anyway in linux.

I tried to run wget -O find_uno.py https://gist.githubusercontent.com/regebro/036da022dc7d5241a0ee97efdf1458eb/raw/find_uno.py python3 find_uno.py as shown in unoserver readme and it only gave me system python path.

What am I missing? Any hints or suggestions are appreciated.

Also if possible could someone explain the sequence of: context, resolver,ctx, smgr, desktop and what do these object means in layman terms?


Solution

  • (Answer adapted from the comments which led to resolution of the problem):

    Be sure to work through the tutorial at http://christopher5106.github.io/office/2015/12/06/openoffice-libreoffice-automate-your-office-tasks-with-python-macros.html.

    From the comments, it sounds like you're stuck somewhere in the tutorial. Did you successfully enter the following in a terminal, and can you see the resulting window:

    soffice --calc  --accept="socket,host=localhost,port=2002;urp;StarOffice.ServiceManager"
    

    You also asked for more information about pylint fakes. After further reading, I do not think this will be relevant for you. Anyway, for example if pylint complains about UNO objects when using a separate python distribution on Windows for static code checking, a workaround is to create a pylint-fakes directory as described at https://stackoverflow.com/a/9616857/5100564. For example, if pylint does not find XActionListener, you could create a file named pylint-fakes/com/sun/star/awt/__init__.py with the following mock code:

    class XActionListener():
        pass