5

I would like to include a third party library into my Python script folder to distribute it all togehter (I am awary of the distribution license, and this library is fine to distribute). This is in order to avoid installing the library on another machine.

Saying I have a script (my_script.py), which calls this external library. I tried to copy this library from the site-packages subdirectory of Python directory into the directory where I have my files, but it seems not to be enough (I think th reason is in the __init__.py of this library which probably needs the folder to be in the PYTHONPATH).

Would it be reasonable to insert some lines of code in my_script.py to temporary append its folder to sys.path in order to make the all things working?

For instance, if I have a structure similar to this:

Main_folder
my_script.py
  /external_lib_folder
   __init__.py
   external_lib.py

and external_lib_folder is the external library I copied from site-packages and inserted in my Main_folder, would it be fine if I write these lines (e.g.) in my_script.py?

import os,sys
main_dir = os.path.dirname(os.path.abspath(__file__))
sys.path.append(main_dir)

EDIT

I ended up choosing the sys.path.append solution. I added these lines to my my_script.py:

import os, sys

# temporarily appends the folder containing this file into sys.path
main_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)),'functions')
sys.path.append(main_dir)

Anyway, I chose to insert this as an edit in my question and accept the answer of Torxed because of the time he spent in helping me (and of course because his solution works as well).

umbe1987
  • 2,894
  • 6
  • 35
  • 63

2 Answers2

7

Python3

import importlib.machinery, imp
namespace = 'external_lib'
loader = importlib.machinery.SourceFileLoader(namespace, '/home/user/external_lib_folder/external_lib.py')
external_lib = loader.load_module(namespace)

# How to use it:
external_lib.function(data_or_something)

This would be an ideal way to load custom paths in Python 3.
Not entirely sure this is what you wanted but It's relevant enough to post an alternative to adding to sys.path.

Python2

In python 2 you could just do (if i'm not mistaken, been a while since i used an older version of Python):

external_lib = __import__('external_lib_folder')

This does however require you to keep the __init__.py and a proper declaration of functions in sad script, otherwise it will fail.
**It's also important that the folder you're trying to import from is of the same name that the __init__.py script in sad folder is trying to import it's sub-libraries from, for instance geopy would be:

./myscript.py
./geopy/
./geopy/__init__.py
./geopy/compat.py
...

And the code of myscript.py would look like this:

handle = __import__('geopy')
print(handle)

Which would produce the following output:

[user@machine project]$ python2 myscript.py 
<module 'geopy' from '/home/user/project/geopy/__init__.pyc'>

[user@machine project]$ tree -L 2
.
├── geopy
│   ├── compat.py
│   ├── compat.pyc
│   ├── distance.py
│   ├── distance.pyc
│   ├── exc.py
│   ├── exc.pyc
│   ├── format.py
│   ├── format.pyc
│   ├── geocoders
│   ├── __init__.py
│   ├── __init__.pyc
│   ├── location.py
│   ├── location.pyc
│   ├── point.py
│   ├── point.pyc
│   ├── units.py
│   ├── units.pyc
│   ├── util.py
│   ├── util.pyc
│   └── version.pyc
└── myscript.py

2 directories, 20 files

Because in __init__.py of geopy, it's defined imports such as from geopy.point import Point which requires a namespace or a folder of geopy to be present.
There for you can't rename the folder to functions and place a folder called geopy in there because that won't work, nor will placing the contents of geopy in a folder called functions because that's not what geopy will look for.

Adding the path to sys.path (Py2 + 3)

As discussed in the comments, you can also add the folder to your sys.path variable prior to imports.

import sys
sys.path.insert(0, './functions')

import geopy
print(geopy)

>>> <module 'geopy' from './functions/geopy/__init__.pyc'>

Why this is a bad idea: It will work, and is used by many. The problems that can occur is that you might replace system functions or other modules might get loaded from other folders if you're not careful where you import stuff from. There for use .insert(0, ...) for most and be sure you actually want to risk replacing system built-ins with "shady" path names.

Torxed
  • 22,866
  • 14
  • 82
  • 131
  • Thanks. I have Python 2.7. I added the line "external_lib = __import__('external_lib_folder')" (of course changing the path to the actual external_lib_folder). I receive "library = ImportError: Import by filename is not supported." – umbe1987 Mar 08 '16 at 08:23
  • @umbe1987 did you add `.py` to the end? If so remove that – Torxed Mar 08 '16 at 08:25
  • No, actually it is the path to the folder which contains the library (in my case is "C:\Umberto\progetti\python_geolocator\geolocator\functions", which contains "geopy" the package I need to include with my project. This folder (package) was copied and pasted from the "site-packages" folder of the Python folder installation, so it has laready its "__init__.py" files in it. So I am doing "geopy = __import__(r'C:\Umberto\progetti\python_geolocator\geolocator\functions')", where the geopy package is put – umbe1987 Mar 08 '16 at 09:12
  • I also looked at "importlib.import_module(name, package=None)", but I sincerely did not get what the "name" stands for. I would like to be able to import the folder "geopy" (which I intend as the package) and its content (which I intend to be the modules) into my main script, so that when I use the "geopy" functions and variables, I will not get errors like "module "geopy" is not defined" or similar. I think this is the way to go, but still did not get what I am supposed to put as argument to "__import__" or "importlib.import_module". Any help would be really appreciated. – umbe1987 Mar 08 '16 at 09:33
  • 1
    @umbe1987 check my update to my answer, it works perfectly. You just need to make sure the folder structure is correct and that the name of the folder matches what the `__init__.py` script tries to import in it. For instance it's doing `from geopy.point import Point` which is basically trying to import from the folder/namespace `geopy` and there for such a folder or namespace must be present or created. (I'm not much for perfect technical descriptions, i usually understand the jist of things. Someone better familiar with the perfect terminology for what happens can exlpain this better). – Torxed Mar 08 '16 at 10:05
  • @umbe1987 do `cd C:\Umberto\gelsia\ambiente\python_geolocator\geolocator\functions` and then do `python` and finally in the interpreter do `x = __import__('geopy')`. – Torxed Mar 08 '16 at 10:13
  • Sorry, but I would like to be able to insert the handle inside the 'main_script.py'. As far as I understand, this should work as long as the __init__.py calls respect a precise structure. What I would like to avoid is to modify the files of the geopy package, just being able to include them in my project a they were in the system path. So, wouldn't be better to temporarily add the 'geopy' folder to the path? Which is the problem in doing s0? – umbe1987 Mar 08 '16 at 11:15
  • 1
    @umbe1987 You can absoluteley add things to the system path, and that would probably work in 99% of all your cases. – Torxed Mar 08 '16 at 11:21
  • 1
    @umbe1987 added a example of how to do it with the `PATH` variable. – Torxed Mar 08 '16 at 11:25
1

What you suggest is bad practice, it is a weak arrangement. The best solution (which is also easy to do) is to package it properly and add an explicit dependency, like this:

from setuptools import setup

setup(name='funniest',
      version='0.1',
      description='The funniest joke in the world',
      url='http://github.com/storborg/funniest',
      author='Flying Circus',
      author_email='flyingcircus@example.com',
      license='MIT',
      packages=['funniest'],
      install_requires=[
          'markdown',
      ],
      zip_safe=False)

This will work if the third party library is on pipy. If it's not, use this:

setup(
    ...
    dependency_links=['http://github.com/user/repo/tarball/master#egg=package-1.0']
    ...
)

(See this explanation for packaging).

DevShark
  • 8,558
  • 9
  • 32
  • 56
  • 4
    **will require python-setuptools** on the machine running this application and secondly will require additional elevated rights to install and possibly a internet connection. The *best solution* is not always what the OP wants. – Torxed Mar 08 '16 at 00:33