Py3 need help resolving import module
#1
Update:  So I have a tentative fix.  I see that when loaded as a library module, the path to the lib folder is added to the sys.path, but when started as a script it isn't.  So I did a sys.path.insert to insert the path to "lib" at the beginning of the sys.path which I think approximates how py2 worked?
---------------------------------------------------------------------------------------------------------

I've been converting some Py2 scripts to to Py3 and my biggest problem has been importing modules.  One has me stumped.

The addon works both as a library and as its own addon via its addon.xml:
Code:
<extension point="xbmc.python.script" library="control.py">
        <provides>executable</provides>
    </extension>
    <extension point="xbmc.python.module" library="lib" />

The folder structure is
- control.py
-lib
--__init__.py
-- module1.py
--module2.py
--more modules.py
--subfolders etc

The problem is for example in module1 it imports module2 or other modules from the subfolders via
Code:
import module2

So when I first converted it and imported module1 in the lib folder from other addon that worked (module1 could import module2).  But when I ran it as a script I got module exceptions.  So naively I patched it by changing imports to relative imports like
Code:
from . import module2
.  That fixed it when run as a script, but now it won't work when importing within the other script (can't resolve relative import).

I'm thinking there is some absolute path I could provide for the import that works in both cases, but can't figure out how to do that.  Reading online there's plenty of info but I don't think the examples cover the case on how paths are managed within Kodi python.

It seems like "lib" is added to the path search space when used as a library module, but not when called directly?  And if so, if many library addons use "lib" how does that work?

scott s.
.
maintainer of skin  Aeon MQ5 mods for post-Gotham Kodi releases:
Matrix see: Aeon MQ5 Mod Matrix release thread
Nexus see: Aeon MQ5 Mod Nexus release thread
Aeon MQ 5 skin and addon repo 11.1.0
Reply
#2
If you really need to use the same addon as a script and a library, then you can refactor it into a 2-level structure like this:

/lib
__init__.py
   /<your_library_name>
  __init__.py
  module1.py
  module2.py
 ...


Then in your script you can do
from lib.<your_library_name> import module1

And in other addons that user your addon as a library:
from <your_library_name> import module1

I'd recommend to use some unique name for <your_library_name> to avoid name conflicts.
Reply
#3
(2020-05-06, 09:55)Roman_V_M Wrote: If you really need to use the same addon as a script and a library, then you can refactor it into a 2-level structure like this:

/lib
__init__.py
   /<your_library_name>
  __init__.py
  module1.py
  module2.py
 ...


Then in your script you can do
from lib.<your_library_name> import module1

And in other addons that user your addon as a library:
from <your_library_name> import module1

I'd recommend to use some unique name for <your_library_name> to avoid name conflicts.
Thanks.  I see what you are advising -- makes sense.

scott s.
.
maintainer of skin  Aeon MQ5 mods for post-Gotham Kodi releases:
Matrix see: Aeon MQ5 Mod Matrix release thread
Nexus see: Aeon MQ5 Mod Nexus release thread
Aeon MQ 5 skin and addon repo 11.1.0
Reply
#4
Hi,
I will resurrect this thread as it is all about imports.
It somehow worked for me in Python 2 but I came to issues when I wanted to maintain the same structure with Python 3.
I have similar but a more advanced case of imports which - as Google shows - is very infuriating for many people.

The addon structure is like this:
python:

myaddon/
--service.py
--addon.xml
--resources/
----__init__.py
----lib/
--------__init__.py
--------common.py
--------globals.py
--------main.py
--------context.py

main.py is main entry point of this addon and includes:
python:

if __name__ == '__main__': # prepare plugin environment

The above structure seems to work if I use following imports inside main.py:
python:

from .common import sub1, sub2, sub3
from resources.lib import globals

However, I also have context.py which is a context menu item, and it is called from addon.xml by using:
python:

<extension point="kodi.context.item">
    <menu id="kodi.core.main">
        <item library="resources/lib/context.py">

and the context.py also includes:
python:

from resources.lib import globals
from resources.lib import common

def main():
    [...]

if __name__ == '__main__':
    main()


The result is that whereas the main.py part is working and all imports are correct, I can't find a way to set imports in context.py to satisfy all import requirements using this structure.
I get either:
1) import error inside context.py (no module found) if I have absolute import;
2) import error inside globals.py which is caused by importing it by context.py if I have relative import;
3) or even: ModuleNotFoundError: No module named 'globals.__main__'; '__main__' is not a package

I workarounded all of those issues by moving context.py to the same location as service.py - which is the root of myaddon.
However I'm not fully sure if this is viable solution as addon structure say that there should be (a single?) file containing the main code for addon. Well, in my case it seems that there are two.
RPi4; LibreElec
Reply
#5
(2020-06-17, 09:34)bkiziuk Wrote: However I'm not fully sure if this is viable solution as addon structure say that there should be (a single?) file containing the main code for addon. Well, in my case it seems that there are two.

I don't see where it is said that you should have a single file containing the main addon code. Those layouts are just examples. And if your addon have several entry points, having all of them in one file violates single responsibility principle. So yes, it is totally fine to have one .py file per entry point. BTW, entry point .py files should be as small as possible with your business logic in imported modules so that a Python interpreter re-uses cached byte-code for imported modules without having to re-compile them every time.
Reply
#6
(2020-06-17, 11:52)Roman_V_M Wrote: So yes, it is totally fine to have one .py file per entry point. BTW, entry point .py files should be as small as possible with your business logic in imported modules so that a Python interpreter re-uses cached byte-code for imported modules without having to re-compile them every time.

Got it! Many thanks.
RPi4; LibreElec
Reply
#7
I spent some tim struggling with module imports yesterday.  I have some common code that loads just fine in my weather addon, but for the life of me could not get it to work the same way in an image plugin.

What is the rationale for different module loading patterns?  It's confusing as all heck, especially with Python 3 being a lot fussier in general about module loading (which I am sure is a good thing).  But really, feels like any kodi addon should behave the same way in terms of fundamentals like this...
Addons I wrote &/or maintain:
OzWeather (Australian BOM weather) | Check Previous Episode | Playback Resumer | Unpause Jumpback | XSqueezeDisplay | (Legacy - XSqueeze & XZen)
Sorry, no help w/out a *full debug log*.
Reply
#8
(2020-06-18, 00:25)bossanova808 Wrote: What is the rationale for different module loading patterns?

It's explained here: https://www.python.org/dev/peps/pep-0328/#id5 (the 2nd item). BTW, this PEP was created in 2003 so it's not like it was all of sudden. Yes, in Python 3 absolute import was made default but developers had plenty of time to prepare for this change. Personally, I've been using absolute import in Python 2 for a long time after having a module name clash with an external library exactly as described in PEP-328.
Reply
#9
That's not quite what I am asking, unless I am misunderstanding. 

My question is - why can I import it in one addon type (weather - using a basic sys.path.append / fropm blah import * approach) but not the same way in another (a plugin.image type).
Addons I wrote &/or maintain:
OzWeather (Australian BOM weather) | Check Previous Episode | Playback Resumer | Unpause Jumpback | XSqueezeDisplay | (Legacy - XSqueeze & XZen)
Sorry, no help w/out a *full debug log*.
Reply
#10
Sorry, but I so far I've seen to evidence that import works differently in different types of Python addons with the same Python version and the the same presence/absence of "from __future__ import absolute_import" statements. Please provide addon samples where this issue occurs.
Reply
#11
Eh, I've gone and updated them now anyway, to the explicit approach.  Can't be bothered going back to just prove the point!
Addons I wrote &/or maintain:
OzWeather (Australian BOM weather) | Check Previous Episode | Playback Resumer | Unpause Jumpback | XSqueezeDisplay | (Legacy - XSqueeze & XZen)
Sorry, no help w/out a *full debug log*.
Reply

Logout Mark Read Team Forum Stats Members Help
Py3 need help resolving import module0