Encapsulating a Pyhon library

Hallo all,

I have started my early research into developing extensions, by looking at the wiki, and the API documentation, in order to wrap my head around the environment.

My sub-goal is to develop an extension which wraps the PyPDF library, exposing its API for use from within macros and other extensions (but mainly macros). Specifically, my end goal is to develop macros that can extract and manipulate data entered in PDF forms, a functionality which is supported by PyPDF.

I wonder if there are tips, guidance, or general thoughts from previous experiences that anyone here would like to share.

Regards,

Is this matter still relevant to you, @gharbeia? Here is my brief report on a related topic:

Some years ago, I completed a project in a similar field. In my case, it was a self-written Python library for converting between ODF and the yWriter writing program.
I have created a pure Python application with a tkinter GUI.
Furthermore, a LibreOffice extension that makes the range of functions accessible directly from LibreOffice Writer and Calc.

I used my own packaging system, which scans the Python code and integrates all imported modules “inline” into a single Python file. That was the easiest way for me to handle the library and application.

To automate versioning and the update process, I also generated the OXT using a build script. However, the OpenOffice Extension Compiler by Bernard Marcelly was a great help in understanding the whole process in the first place.

Basically, using a Python macro in an extension is no big deal. However, you should consider how your LibreOffice installation calls the Python interpreter and which version number it has. Under Windows, LibreOffice comes with its own Python installation, which is reasonably up to date provided that the user updates LibreOffice regularly (which I don’t do, for example, since version 7.6 runs satisfactorily for me).
I don’t know how it works on Mac OS. On Linux I believe LibreOffice calls up the system’s Python interpreter. This can lead to surprises, because some LTS distributions (in my case: openSuse leap) have outdated Python versions installed.

A particular challenge for me was a variant for OpenOffice. My Python library requires Python 3, while OpenOffice on Windows still comes with Python 2. In this case, I wrote a StarBasic macro that calls the Python 3 interpreter installed by the user on Windows with the Python 3 application coming with the extension, passing the correct parameters. A bit nerdy, but it worked perfectly for me in everyday use (I use AOO 3.4 for novel writing because of a commercial spell checker extension that is not available for LibreOffice).

I am writing all this to draw your attention to a few pitfalls. The PyPDF library requires Python 3.9+, which is important to keep in mind. It is also subject to a private license, which is probably not a serious obstacle, but can still complicate matters.

If I understand you correctly, you want to package the PyPDF library into a LibreOffice extension. I can imagine that this would work, but I still have to ask what the advantage of doing so would be. For third-party macros, it is not necessarily easier to access a Python library installed in this way than if it were installed normally, i.e., with PyPi. Above all, I see the problem here with updates and version incompatibilities.

My advice: first create an application macro that accesses a regularly installed PyPDF library, and then try step by step to move this library elsewhere.

Good luck,
Peter

1 Like

Now I was curious and wanted to try out that Python library encapsulation thing. To do this, I created a minimal extension named encapsulated_python_lib. Here it is in the 7z file manager:

encapsulated_python_lib-1.0.0.oxt

Screenshot 2025-12-09 173055

In the encapsulated_python_lib subdirectory, I put a simple Python module named encapsulated_lib.py. It just provides a function that returns the version number of the running Python interpreter.

encapsulated_lib.py

import sys

def get_python_version():
    return f'Python {sys.version_info.major}.{sys.version_info.minor}'

I installed the extension …

Screenshot 2025-12-09 195018

… and created a Python macro in LibreOffice (the APSO extension is required):

test_macro.py

from apso_utils import msgbox
import os
import sys
import uno

def make_accessible_for_import(extension, lib_dir):
    oPackageInfoProvider = XSCRIPTCONTEXT.getComponentContext().getByName(
       "/singletons/com.sun.star.deployment.PackageInformationProvider"
    )
    sPackageLocation = oPackageInfoProvider.getPackageLocation(extension)
    path_to_extension = uno.fileUrlToSystemPath(sPackageLocation).replace('\\', '/')
    sys.path.insert(0, f'{path_to_extension}/{lib_dir}')    

def show_python_version():
    make_accessible_for_import(
        'org.peter88213.encapsulated_python_lib',
        'encapsulated_python_lib'
    )
    from encapsulated_lib import get_python_version
    msgbox(get_python_version())

I executed the test macro via APSO …

Screenshot 2025-12-09 173907

… and it worked!

Screenshot 2025-12-09 173940

1 Like

Thank you, @Peter_T , for sharing these tips from your experience.

My immediate users would be Linux and Mac users. I have an idea about the headache the discrepancy in the versions of Python can cause. But thanks for pointing it out.

My aim with encapsulating the PyPDF in an extension was to reduce the components needed to be installed by users, assuming of course that all is well with the Python environment, and to keep the PyPDF version on which the functionality within LibreOffice is built stable against possible changes in the first.

I was also wondering if accessing the API of PyPDF from the extension would be simplified, but thanks to insight from you, I now know that there’s no difference.

And thanks for this tutorial too!

I’m still interested in this project, but I had not made progress since my message. This message of yours rekindled my enthusiasm.

I’ll post here when/if I make progress and share my notes too.

I think encapsulating a large Python library in an extension could be beneficial if you distribute your actual application as an extension. Then you can separate your software from the library and reduce data transfer if you offer updates frequently. Or, conversely, you can provide library updates without updating your application extension.

However, when it comes to using the Python library for your own macros, it is much easier to store it in the user profile under Scripts/python, where the macros can import it without any additional effort. I assume that anyone who can program Python macros is also capable of copying the library there.

The major challenges will arise when you attempt to extract data from PDF files and insert it into ODF documents. There are some interesting options available. I mentioned my yw-cnv extension above, where I used yWriter project files as XML databases that contain a lot of metadata and additional structures from the research area in addition to the novel text. Similar to a content management system, my extension uses this data to generate ODT and ODS documents, which it can also convert back after editing.

I had the choice of writing the data to ODF documents via UNO API or using pure Python to generate ODF files and then loading them into Open/LibreOffice. The latter ran significantly faster and also offered the possibility of standalone Python programs, as mentioned above.

But that’s off-topic now, because your thread is about library encapsulation, isn’t it? I suggest you open a new thread in which you refer to your public repository when the time comes. On GitHub, for example, there are a number of communication channels for those who want to participate in discussions, testing, etc.
And in any case, a comprehensive specification would be helpful if you are interested in collaborating with others. User stories and all that, you know what I mean.

Cheers,
Peter