Brian Ekins
Brian Ekins

Reputation: 147

Problems with relative import and Python 3

First, I need to describe the environment I'm writing for. I'm writing Python code that will be loaded and executed by a Python runtime running within a CAD application. The CAD application uses Python as its scripting engine. As a result, I don't have access to the Python runtime and as a good citizen to all other scripts shouldn't modify any system settings. My script is just one of many that are loaded and running.

This all works fine except when I want to use non-standard libraries. In that case, I need to install a local copy of the library for my script to access. The problem I'm having is that most libraries expect to be installed and added to the sys path which is something I shouldn't do because it could create conflicts with what other scripts are doing. What I'm attempting to do instead is to set up a local copy of the library(s) and then edit their source so their imports are relative and they don't depend on the sys path. That way my program will have its own local copy of the libraries and not depend on anything else and won't disturb any other scripts.

I'm using the -t option of PIP to install Requests and PyOpenSSL into a "Packages" subfolder in my script folder. Here's an abbreviated list of what I have.

RequestsTest/
    RequestsTest.py 
    Packages/
        OpenSSL/
            cryptography/
                x509/
                    __init__.py
                    base.py
                hazmat/
                    __init__.py
                    backends/
                        __init__.py
                        interfaces.py
                        openssl/
                            __init__.py
                            backend.py
                            x509.py
            OpenSSL/
                __init__.py
                SSL.py
        Requests/
            chardet/
                __init__.py
            requests/
                __init__.py
            urllib3/
                __init__.py
                request.py
                contrib/
                    __init__.py
                    pyopenssl.py
                util/
                    __init__.py
                    request.py
                    ssl_.py

Although it's tedious to track down the various import statements and make then relative, it does seem to work. However, I'm having problems with one particular set of imports.

In Packages/Requests/urllib3/contrib/pyopenssl.py it contains the following imports, which I've modified:

from ....OpenSSL.OpenSSL import SSL 
from ....OpenSSL.cryptography import x509

They were originally:

from OpenSSL import OpenSSL.SSL
from cryptography import x509

I get the error "ImportError: No module named 'OpenSSL'" for the first line and "ImportError: No module named 'cryptography'" for the second line. I'm fairly certain the path is correct because if I change the number of dots I get the no module named error but it lists the full path of the what it's trying to load and not just the name of the module.

I would appreciate some help with this specific issue but can also use some overall advice of how to set up and use private copies of libraries. Remember that my program is just one of many that the system is loading changing the system or setting up a virtual environment is not an option.

Upvotes: 4

Views: 242

Answers (1)

Chris Hunt
Chris Hunt

Reputation: 4030

Check out the localimport module, which seems to be a solution for your particular use case. From the README:

Given your Python script, application or plugin comes with a directory that contains modules for import, you can use localimport to keep the global importer state clean.

app.py
res/modules/
  some_package/
    __init__.py


# app.py
with localimport('res/modules') as _importer:
    import some_package
    assert 'some_package' not in sys.modules

The tagline is "Isolated import of Python Modules for embedded applications." so it seems pretty relevant.

When using that module, the following may help keep things neat:

  1. Put your actual script logic into its own file.
  2. Have a wrapper script (which will be the one loaded by the CAD software) which does localimport as mentioned in the README then does a relative import of your module. If your module gets big enough maybe put it into its own package and consume it in the same way as everything else (just do from RequestsTest import * in the body of the with localimport(): ....
  3. Try to have a clear boundary between the source code you write and the final organized set of files required to use that source code in the context of the CAD Python runtime. It is OK to have a build/packaging step that creates the localimport script, downloads the required packages, etc. It's better even because then it is automated and not something that was done manually that someone in the future may have to recreate.

Upvotes: 3

Related Questions