I need to ship a collection of Python programs that use multiple packages stored in a local Library
directory: the goal is to avoid having users install packages before using my programs (the packages are shipped in the Library
directory). What is the best way of importing the packages contained in Library
?
I tried three methods, but none of them appears perfect: is there a simpler and robust method? or is one of these methods the best one can do?
In the first method, the
Library
folder is simply added to the library path:import sys import os sys.path.insert(0, os.path.join(os.path.dirname(__file__), 'Library')) import package_from_Library
The
Library
folder is put at the beginning so that the packages shipped with my programs have priority over the same modules installed by the user (this way I am sure that they have the correct version to work with my programs). This method also works when theLibrary
folder is not in the current directory, which is good. However, this approach has drawbacks. Each and every one of my programs adds a copy of the same path tosys.path
, which is a waste. In addition, all programs must contain the same three path-modifying lines, which goes against the Don't Repeat Yourself principle.An improvement over the above problems consists in trying to add the
Library
path only once, by doing it in an imported module:# In module add_Library_path: sys.path.insert(0, os.path.join(os.path.dirname(__file__), 'Library'))
and then to use, in each of my programs:
import add_Library_path import package_from_Library
This way, thanks to the caching mechanism of CPython, the module
add_Library_path
is only run once, and theLibrary
path is added only once tosys.path
. However, a drawback of this approach is thatimport add_Library_path
has an invisible side effect, and that the order of the imports matters: this makes the code less legible, and more fragile. Also, this forces my distribution of programs to inlude anadd_Library_path.py
program that users will not use.Python modules from
Library
can also be imported by making it a package (empty__init__.py
file stored inside), which allows one to do:from Library import module_from_Library
However, this breaks for packages in
Library
, as they might do something likefrom xlutils.filter import …
, which breaks becausexlutils
is not found insys.path
. So, this method works, but only when including modules inLibrary
, not packages.
All these methods have some drawback.
Is there a better way of shipping programs with a collection of packages (that they use) stored in a local Library
directory? or is one of the methods above (method 1?) the best one can do?
PS: In my case, all the packages from Library
are pure Python packages, but a more general solution that works for any operating system is best.
PPS: The goal is that the user be able to use my programs without having to install anything (beyond copying the directory I ship them regularly), like in the examples above.
PPPS: More precisely, the goal is to have the flexibility of easily updating both my collection of programs and their associated third-party packages from Library
by having my users do a simple copy of a directory containing my programs and the Library
folder of "hidden" third-party packages. (I do frequent updates, so I prefer not forcing the users to update their Python distribution too.)
Messing around with
sys.path()
leads to pain... The modern package template and Distribute contain a vast array of information and were in part set up to solve your problem.What I would do is to set up setup.py to install all your packages to a specific site-packages location or if you could do it to the system's site-packages. In the former case, the local site-packages would then be added to the PYTHONPATH of the system/user. In the latter case, nothing needs to changes
You could use the batch file to set the python path as well. Or change the python executable to point to a shell script that contains a modified PYTHONPATH and then executes the python interpreter. The latter of course, means that you have to have access to the user's machine, which you do not. However, if your users only run scripts and do not import your own libraries, you could use your own wrapper for scripts:
And the
/path/to/my/python
script would be something like:I think you should have a look at path import hooks which allow to modify the behaviour of python when searching for modules.
For example you could try to do something like kde's scriptengine does for python plugins[1]. It adds a special token to
sys.path
(like"<plasmaXXXXXX>"
withXXXXXX
being a random number just to avoid name collisions) and then when python try to import modules and can't find them in the other paths, it will call your importer which can deal with it.A simpler alternative is to have a main script used as launcher which simply adds the path to
sys.path
and execute the target file(so that you can safely avoid putting thesys.path.append(...)
line on every file).Yet an other alternative, that works on python2.6+, would be to install the library under the per-user site-packages directory.
[1] You can find the source code under
/usr/share/kde4/apps/plasma_scriptengine_python
in a linux installation with kde.