2020-10-04

53: Import Any Module into Your LibreOffice Python Macro, Part 1

<The previous article in this series | The table of contents of this series | The next article in this series>

Or into any module imported by your macro. This part is for except for importing in-document modules.

Topics


About: UNO (Universal Network Objects)
About: LibreOffice
About: Apache OpenOffice
About: The Python programming language

The table of contents of this article


Starting Context


  • The reader has a basic knowledge on LibreOffice or Apache OpenOffice.
  • The reader has a basic knowledge on the Python programming language.

Target Context


  • The reader will know how to import any module (except any in-document module, for this part) into his or her LibreOffice or Apache OpenOffice Python macro module.

Orientation


Using an external full Python has been addressed in a previous article.

Creating your user-owned or application-owned Python macro has been addressed in a previous article.

Creating your in-document Python macro has been addressed in a previous article.

Creating your in-extension Python macro has been addressed in a previous article.

The details of Python macro programming will be dug into in some future articles.


Main Body

Stage Direction
Here are Hypothesizer 7, Objector 53A, and Objector 53B in front of a computer.


1: Trying to Import an Arbitrary Module into a Python Macro Module


Hypothesizer 7
You cannot just import an arbitrary module into your Python macro module.

Objector 53A
Certainly! I can't import 'SciPy'!

Hypothesizer 7
Sir, actually, I am not talking about that, here.

Objector 53A
But I can't!

Hypothesizer 7
That is because you have not installed the library into the Python used by the LibreOffice or Apache OpenOffice.

Objector 53A
Of course, I have!

Hypothesizer 7
. . . You may need to check whether your intended Python is really used by your LibreOffice or Apache OpenOffice.

Objector 53A
I don't need; I strongly feel that it is.

Stage Direction
Objector 53A nods assuredly.

Hypothesizer 7
. . . Well, this article is about modules that are not incorporated into the Python, where "incorporated into the Python" typically (but not necessarily) means installed by 'pip'. You know, any incorporated-into-the-Python module is able to be imported without any hassle, because it is a part of the Python, so to speak.

Objector 53A
I don't import any module that is not incorporated into the Python.

Hypothesizer 7
. . . You create some modules in your project, and one of them is imported into another of them, and so on, I presume?

Objector 53A
I never do such a vain thing as creating multiple modules.

Stage Direction
Objector 53A nods condescendingly.

Hypothesizer 7
. . . Do you jumble the whole code into a single module?

Objector 53A
I don't need to edit multiple files.

Hypothesizer 7
But the single file can be a long jumble.

Objector 53A
I don't need to wonder whether an artifact belongs to 'Module1' or to 'Module2'.

Hypothesizer 7
You would need to wonder only because your modularization was bad: module names like 'Module1' are bad, because they do not represent any criteria whether an artifact belongs there.

Objector 53A
Simple is the best; having the single file is the simplest; so, having the single file is the best.

Stage Direction
Objector 53A nods patronizingly.

Hypothesizer 7
. . . I happen to disagree with every step of your syllogistic.

Besides, don't you have a library project that is used by multiple projects?

Objector 53A
Huh? What is that for?

Hypothesizer 7
. . . You do not want to write the same logic multiple times, don't you?

Objector 53A
I don't write it multiple times; I just copy it.

Hypothesizer 7
. . . Well, if that is your style, this article will be irrelevant to you.


2: It Is All about Setting the Modules Path Except for In-Document Imported Modules, but . . .


Hypothesizer 7
As it should be, it is all about setting the modules path except for in-document imported modules: if the path has been set, the module should be able to be imported.

Objector 53B
I set the 'PYTHONPATH' environment variable, which didn't work!

Hypothesizer 7
Madam, that does not work; so, let us see what do work.


3: Setting Any Predictable Modules Path


Hypothesizer 7
We can set any predictable modules path in the 'PYUNO_LOADER_PYTHONPATH' variable in the 'pythonloader.unorc' or 'pythonloader.uno.ini' file in the LibreOffice or Apache OpenOffice 'program' directory.

Objector 53B
What do you mean by "predictable"? Something may be predictable by a prophet, but not by me.

Hypothesizer 7
That is a good question. In fact, it is about whether the path is predictable by you.

Objector 53B
Huh? Are you nuts? How do you know whether it's predictable by me?

Hypothesizer 7
I do not, and do not need to: if you deem it to be predictable, you are recommended to use this method.

Objector 53B
Huh? That sounds so slipshod . . .

Hypothesizer 7
You will set a constant to the aforementioned variable; if that is OK for you, it will be OK for you.

Objector 53B
Huh? Then, what paths can be non-"constant"?

Hypothesizer 7
I do not know whether you deem these to be non-constant, but for example, the user-owned macros base path depends on the operating system, the LibreOffice or Apache OpenOffice version, and the user.

Objector 53B
Ah, a path like '/home/objectror53B/.config/libreoffice/4/user/Scripts/python' in Linux . . .

Hypothesizer 7
If you are OK with setting the path to the variable, it will be OK for you.

Objector 53B
Maybe.

Hypothesizer 7
As another example, the Python macros base path of an extension is like '/home/objectror53B/.config/libreoffice/4/user/uno_packages/cache/uno_packages/lu49122vpj5e.tmp_/theBiasPlanet.pythonEnvironmentChecker.unoExtension.oxt/Scripts/python'.

Objector 53B
. . . What is that "~.tmp_" thing?

Hypothesizer 7
That is a directory that has been automatically created when the extension was installed. The name changes per installation. Is that predictable? That is what you decide.

Objector 53B
Hmm . . .

Hypothesizer 7
Anyway, note that the path to be set to the variable is really a URL.


4: Dynamically Setting Any Modules Path


Hypothesizer 7
In fact, as you may know, any modules path can be dynamically set into the 'sys.path' variable.

Objector 53B
I know . . ., so, should I create a logic that looks for and set the "unpredictable" paths?

Hypothesizer 7
If you are willing to, you can.

However, if the path to be set is a macros base path, the method introduced in the next section may be better.


5: Letting the Accessed Modules Base Paths Be Automatically Set


Hypothesizer 7
This is a useful knowledge: any macro module is loaded by 'pythonscript.py' in the 'program' directory of the LibreOffice or Apache OpenOffice product directory.

As 'pythonscript.py' knows the modules base path when a macro module is being loaded, we can tweak 'pythonscript.py' to let it set the modules base path to 'sys.path'.

Objector 53B
Do you embark on such a hack?

Hypothesizer 7
Actually, I do. This is the tweak ('pythonscript.py' is of LibreOffice 6.4.4).

Add this after "from com.sun.star.uri.RelativeUriExcessParentSegments import RETAIN".

@Python Source Code
from collections import OrderedDict

Add this after "iENABLE_EDIT_DIALOG=False # offers a minimal editor for editing".

@Python Source Code
s_additionalModulesRootUrlToDummyMap = OrderedDict ()
s_fileContentsProvider = uno.getComponentContext().ServiceManager.createInstanceWithContext ("com.sun.star.ucb.FileContentProvider", uno.getComponentContext())

Add this after "entry.module.__dict__[GLOBAL_SCRIPTCONTEXT_NAME] = self.scriptContext" in the 'getModuleByUrl' method.

@Python Source Code
            entry.module.__dict__ ["s_sourceFileUrl"] = url

Add this after "ret[ lastElement( j ) ] = Package( paths, transientPathElement )" in the 'getPackageName2PathMap' function.

@Python Source Code
            for l_modulesRootUrl in paths:
                if s_additionalModulesRootUrlToDummyMap.get (l_modulesRootUrl) is None:
                    l_modulesRootDirectoryPathString = s_fileContentsProvider.getSystemPathFromFileURL (l_modulesRootUrl)
                    if l_modulesRootDirectoryPathString != "":
                        log.debug ("### Added path: " + l_modulesRootDirectoryPathString)
                        sys.path.append (l_modulesRootDirectoryPathString)
                        s_additionalModulesRootUrlToDummyMap.update ({l_modulesRootUrl: False})
                    else:
                        log.debug ("### Unhandled URL: " + l_modulesRootUrl)
                else:
                    None

Add this after "self.dirBrowseNode = DirBrowseNode( self.provCtx, LANGUAGENAME, rootUrl )" in the 'PythonScriptProvider' function.

@Python Source Code
                l_modulesRootUrl = rootUrl
                if s_additionalModulesRootUrlToDummyMap.get (l_modulesRootUrl) is None:
                    l_modulesRootDirectoryPathString = s_fileContentsProvider.getSystemPathFromFileURL (l_modulesRootUrl)
                    if l_modulesRootDirectoryPathString != "":
                        log.debug ("### Added path: " + l_modulesRootDirectoryPathString)
                        sys.path.append (l_modulesRootDirectoryPathString)
                        s_additionalModulesRootUrlToDummyMap.update ({l_modulesRootUrl: False})
                    else:
                        log.debug ("### Unhandled URL: " + l_modulesRootUrl)
                else:
                    None

Objector 53B
. . .

Hypothesizer 7
You know, of course, a macro has to be once invoked (even as a dummy) for the base path to be set.

For example, a user-owned macro cannot import any module in an extension, unless at least one macro in the extension has been invoked.

Objector 53B
Of course. On the other hand, the user-owned macro can import any user-owned module, because itself has been invoked.

Hypothesizer 7
Yes.


6: Importing Any In-Document Module Is Another Story


Hypothesizer 7
Importing any in-document module is another story because the module does not exist as an operating system file.

Objector 53B
Of course. The module is embedded inside the document file. . . . So, what should I do?

Hypothesizer 7
We will see that in >the next part of this article.

Objector 53B
I warn you that I won't accept storing the module file somewhere, even if temporarily.

Hypothesizer 7
We will not do such a thing.


References


<The previous article in this series | The table of contents of this series | The next article in this series>