Using APSO to embed scripts in python, how to import functions from one module to another?

Using APSO to embed scripts in python, how to import functions from one module to another?

I have two modules, one named ‘other_module’ , with this content:

# coding: utf-8
import getpass

def get_username():
    return getpass.getuser()

And a module named ‘Main_Module’, with this content:

# -*- coding: UTF-8 -*-
from __future__ import unicode_literals
import uno
from com.sun.star.awt import MessageBoxButtons as MSG_BUTTONS
import other_module

CTX = XSCRIPTCONTEXT.getComponentContext()
SM = CTX.ServiceManager

def create_instance(name, with_context=False):
    if with_context:
        instance = SM.createInstanceWithContext(name, CTX)
    else:
        instance = SM.createInstance(name)
    return instance


def msg(message, title='LibreOffice', buttons=MSG_BUTTONS.BUTTONS_OK, type_msg='INFOBOX'):
    toolkit = create_instance('com.sun.star.awt.Toolkit')
    parent = toolkit.getDesktopWindow()
    mb = toolkit.createMessageBox(parent, type_msg, buttons, title, str(message)+'\n\n')
    return mb.execute()

def show_info(*args):
    infoText = other_module.get_username()
    #infoText = 'hello'
    result = msg(infoText, 'Title', 1, 'INFOBOX')
    return

how do I call the get_username() function of the ‘other_module’ module, through the ‘Main_Module’?

Here is an example file
test.ods (9,3,KB)

Hello,

Possibly this → Import from second, embedded in document, python module

Looking at this ‘testpÿ.odt’ example file:
https://forum.openoffice.org/en/forum/viewtopic.php?t=55246#p438591

When opening the APSO manager, it only displays the ‘macrotest’ module, but when clicking the ‘Go!’ button, the macro runs correctly.

So I used the same code from that ‘testpÿ.odt’ file to make a new ‘test.ods’ file

I create a module by APSO named ‘macrotest’ with this code

# -*- coding: utf-8 -*-
from __future__ import unicode_literals

import sys
from os.path import dirname
from unohelper import fileUrlToSystemPath

if not 'test_utils' in sys.modules:
    doc = XSCRIPTCONTEXT.getDocument()
    url = fileUrlToSystemPath('{}/{}'.format(doc.URL,'Scripts/python/pythonpath'))
    sys.path.insert(0, url)
import test_utils

def doit(event=None):
    lib_folder = dirname(test_utils.__file__)
    test_utils.msgbox("Importing from <pythonpath> successful!")

and create another module by APSO named ‘test_utils’ with this code

from __future__ import unicode_literals

import uno
from com.sun.star.awt.MessageBoxType import MESSAGEBOX, INFOBOX, ERRORBOX, WARNINGBOX, QUERYBOX

def createUnoService(service, ctx):
    return ctx.ServiceManager.createInstanceWithContext(service, ctx)

def msgbox(message, titre="Message", boxtype='message', boutons=1):
    ctx = uno.getComponentContext()
    win = createUnoService("com.sun.star.frame.Desktop", ctx).getCurrentFrame().ContainerWindow
    types = {'message': MESSAGEBOX, 'info': INFOBOX, 'error': ERRORBOX,
             'warn': WARNINGBOX, 'query': QUERYBOX}
    tk = createUnoService("com.sun.star.awt.Toolkit", ctx)
    box = tk.createMessageBox(win, types[boxtype], boutons, titre, message)
    return box.execute()

The two codes are identical to the example file ‘testpÿ.odt’
I create the button and attach the ‘doit’ function to the button event.

Does not work, displays error message.


Unzipping and looking inside the ‘testpÿ.odt’ file:
the file ‘macrotest.py’ is in the folder ‘…testpÿ.odt_FILES/Scripts/python
and the file ‘test_utils.py’ is in the folder ‘…testpÿ.odt_FILES/Scripts/python/pythonpath


Doing the same with my test.ods file, the two modules are in the folder: ‘…test.ods_FILES/Scripts/python

So I made a new file ‘test_2.ods’ and through the APSO manager I just created a single module named ‘macrotest’. I closed the file and manually inserted the file ‘test_utils.py’ inside the ods file, in the path:
‘…test_2.ods_FILES/Scripts/python/pythonpath’

So the script works!


Does anyone know why this error happens and how to fix it?


New examples:
test (both modules were created by APSO).ods (9,9,KB)
test_2(manual insert module test_utils).ods (10,0,KB)

Hello,

This is not an answer and should be an edit to the original question or a comment.

Hello,

The sample contains a pythonpath folder which contains test_utils. Also the manifest.xml file in the /META-INF/ needs updating. With this all works.

Returned working sample →
test (both modules were created by APSO).ods (9.9 KB)

You can unzip the .ods file to see those changes. Do not know of method for Apso to make these changes. Did it manually to the archive.

IHMO, its a kind of bug if it doesnt work without this dirty hacks on sys.path, but anyway here some slightly changes to makrotest.py:

from __future__ import unicode_literals


try:
    import test_utils
    
except ImportError:
    
    from sys import path
    from unohelper import fileUrlToSystemPath as uri2path    
    doc = XSCRIPTCONTEXT.getDocument()
    p = uri2path(f'{doc.URL}/Scripts/python/pythonpath/')
    path.append( p )
    import test_utils

def doit(event=None):
    test_utils.msgbox(f"Import from {p} was successfull !")

That’s a good solution.
But in your example the variable ‘p’ is not defined inside the function ‘doit’

In the python script, I changed the module path to /Scripts/python

That’s how it works in APSO

Thank you all

from __future__ import unicode_literals
import uno

try:
    import test_utils

except ImportError:
    from sys import path   
    doc = XSCRIPTCONTEXT.getDocument()
    p = uno.fileUrlToSystemPath(f'{doc.URL}/Scripts/python/')
    path.append( p )
    import test_utils

def doit(event=None):
    test_utils.msgbox(f"Import was successfull !")

yes my fault, but in case except ImportError: its in the modules-Namespace.

APSO hides the pythonpath subdirectory. In order to fix this, I once submitted an APSO pull request, but the developer rejected the modified code, saying that APSO is designed for simple macros only, not for extension development or other large projects where one module imports another.

So, to answer your question:

The proper way is to not use APSO, and instead do things the direct way using pythonpath. Yes, it’s possible to mess with sys.path to get it to work anyway, but I would not recommend it.

I agree with the developers, if you want to do something serious project, then put on github or similar platforms and let it install the python-way with:

pip install <your_package> …options

or … maybe as Extension.oxt if its strict related to Libreoffice.
there is no need to put everything inside LO.

APSO is well (enough) for general Users.

I’m confused by your comments. First you wrote:

But then you say APSO should hide pythonpath subdirectories in order to make it more difficult to edit files from there. What is the benefit of that, and are you thinking of some other way to import modules instead? My understanding is that the APSO developer believes modules should not be imported at all using the tool, but it sounds like you’re saying something different.

I didnt say that literally, I say you should not try to embed more than a single …py -script into Dokuments, and you could do really sophisticated stuff below a limit of (eg) 1.5 kloc to 2 kloc!!

1.5 kloc in a single python script - yuck! I would much rather split up a project of that size into modules.

The original reason for my pull request was to show pythonpath under My Macros, but I suppose it would be a similar case for scripts embedded in a document. Either way, I find it good practice to split code into modules, even if it’s only 100 lines of code or less. But APSO discourages this.

In retrospect, My Macros and embedded scripts are different in that pythonpath does not work automatically for embedded scripts. My answer may have caused confusion because I did not distinguish between these two scenarios.