I'm dealing with a Python library that does not define the __version__ variable (sqlalchemy-migrate), and I want to have different behavior in my code based on what version of the library I have installed.
Is there a way to check at runtime what version of the library is installed (other than, say, checking the output of pip freeze)?
This being Python, the accepted way of doing this is generally to call something in the library that behaves differently depending on the version you have installed, something like:
import somelibrary
try:
somelibrary.this_only_exists_in_11()
SOME_LIBRARY_VERSION = 1.1
except AttributeError:
SOME_LIBRARY_VERSION = 1.0
A more elegant way might be to create wrapper functions.
def call_11_feature():
try:
somelibrary.this_only_exists_in_11()
except AttributeError:
somelibrary.some_convoluted_methods()
somelibrary.which_mimic()
somelibrary.the_11_feature()
pkg_resources may help, but you'll need to use the package name:
>>> import pkg_resources
>>> env = pkg_resources.Environment()
>>> env['sqlalchemy-migrate'][0].version
'0.6.2.dev'
If the library doesn't know its own version, then you are basically SOL. However, if one of the versions you want to support would raise an exception if the code went down the "wrong" path, you could use a try/except block.
Occasionally you can evaluate the path of the library and it will be in there somewhere... /usr/lib/python2.6/site-packages/XlsXcessive-0.1.6-py2.6.egg
Related
I recently created and published a python package called ADCT.
Link to package as zip download so you can see what I mean: https://pypi.org/project/ADCT/#modal-close
I went ahead and published it and I was able to pip install it on my local machine. In the package, there is an object itself called ADCT. What code snippet, other than "import ADCT" do I run to call the object ADCT? Do I have to rename the object to something else since it could be a collision error? I know this is embarrassing since its my package but any help would be appreciated.
The package and the object will likely to end up in different namespaces. Example:
from ADCT import DataObj
adct_instance = DataObj.ADCT()
Another way to deal with duplicated names (not very helpful in this case, just for the reference) is to use import .. as, e.g.:
import ADCT as ADCT_package
adct_instance = ADCT_package.DataObj.ADCT()
Coming from a C++ world I got used to write conditional compilation based on flags that are determined at compilation time with tools like CMake and the like. I wonder what's the most Pythonic way to mimic this functionality. For instance, this is what I currently set depending on whether a module is found or not:
import imp
try:
imp.find_module('petsc4py')
HAVE_PETSC=True
except ImportError:
HAVE_PETSC=False
Then I can use HAVE_PETSC throughout the rest of my Python code. This works, but I wonder if it's the right way to do it in Python.
Yes, it is ok. You can even issue an import directly, and
use the modulename itself as the flag - like in:
try:
import petsc4py
except ImportError
petsc4py = None
And before any use, just test for the truthfulness of petsc4py itself.
Actually, checking if it exists, and only then trying to import it, feels unpythonic due to the redundancy, as both actions trigger an ImportError all the same. But having a HAVE_PETSC variable for the checkings is ok - it can be created after the try/except above with HAVE_PETSC = bool(petsc4py)
The way you're doing it is more-or-less fine. In fact, The python standard library uses a similar paradigm of "try to import something and if it's not valid for some reason then set a variable somehow" in multiple places. Checking if a boolean is set later in the program is going to be faster than doing a separate try/except block every single time.
In your case it would probably just be better to do this, though:
try:
import petsc4py
HAVE_PETSC = True
except ImportError:
HAVE_PETSC = False
What you have works on a paradigm level, but there's no real reason to go through importlib in this case (and you probably shouldn't use imp anyway, as it's deprecated in recent versions of python).
Is it possible to tell Python 2.7 to only parse a function definition if a package exists?
I have a script that is run on multiple machines. There are some functions defined in the script that are very nice to have, but aren't required for the core operations the script performs. Some of the machines the script is run on don't have the package that the function imports, (and the package can't be installed on them). Currently I have to comment out the function definition before cloning the repo onto those machines. Another solution would be to maintain two different branches but that is even more tedious. Is there a solution that prevents us from having to constantly comment out code before pushing?
There are already solutions for when the function is called, such as this:
try:
someFunction()
except NameError:
print("someFunction() not found.")
Function definitions and imports are just code in Python, and like other code, you can wrap them in a try:
try:
import bandana
except ImportError:
pass # Hat-wearing functions are optional
else:
def wear(hat):
bandana.check(hat)
...
This would define the wear function only if the bandana module is available.
Whether this is a good idea or not is up to you - I think it would be fine in your own scripts, but you might not want to do this in code other people will use. Another idea might be to do something like this:
def wear(hat):
try:
import bandana
except ImportError:
raise NotImplementedError("You need the bandana package to wear hats")
else:
bandana.check(hat)
...
This would make it clearer why you can't use the wear function.
A somewhat improved solution is as follows:
In file header:
try:
# Optional dependency
import psutil
except ImportError as e:
psutil = e
Later in the beginning of your function or inside __init__ method:
if isinstance(psutil, ImportError):
raise psutil
Pros: you get the original exception message when you access optional functionality. Just as if you've did simply import psutil
Today I tried using pyPdf 1.12 in a script I was writing that targets Python 2.6. When running my script, and even importing pyPdf, I get complaints about deprecated functionality (md5->hashsum, sets). I'd like to contribute a patch to make this work cleanly in 2.6, but I imagine the author does not want to break compatibility for older versions (2.5 and earlier).
Searching Google and Stack Overflow have so far turned up nothing. I feel like I have seen try/except blocks around import statements before that accomplish something similar, but can't find any examples. Is there a generally accepted best practice for supporting multiple Python versions?
There are two ways to do this:
(1) Just like you described: Try something and work around the exception for old versions. For example, you could try to import the json module and import a userland implementation if this fails:
try:
import json
except ImportError:
import myutils.myjson as json
This is an example from Django (they use this technique often):
try:
reversed
except NameError:
from django.utils.itercompat import reversed # Python 2.3 fallback
If the iterator reversed is available, they use it. Otherwise, they import their own implementation from the utils package.
(2) Explicitely compare the version of the Python interpreter:
import sys
if sys.version_info < (2, 6, 0):
# Do stuff for old version...
else:
# Do 2.6+ stuff
sys.version_info is a tuple that can easily be compared with similar version tuples.
You can certainly do
try:
import v26
except ImportError:
import v25
Dive Into Python—Using Exceptions for Other Purposes
Multiple versions of Python are supported here. You can a) conditionally use the newer version, which takes a little work, or b) turn off the warnings, which should really be the default (and is on newer Pythons).
How can you raise an exception when you import a module that is less or greater than a given value for its __version__?
There are a lot of different ways you could do it, but I feel like there must be some really simple way that eludes me at the moment. In this case the version number is of the format x.x.x
Python comes with this inbuilt as part of distutils. The module is called distutils.version and is able to compare several different version number formats.
from distutils.version import StrictVersion
print StrictVersion('1.2.2') > StrictVersion('1.2.1')
For way more information than you need, see the documentation:
>>> import distutils.version
>>> help(distutils.version)
If you are talking about modules installed with easy_install, this is what you need
import pkg_resources
pkg_resources.require("TurboGears>=1.0.5")
this will raise an error if the installed module is of a lower version
Traceback (most recent call last):
File "tempplg.py", line 2, in <module>
pkg_resources.require("TurboGears>=1.0.5")
File "/usr/lib/python2.5/site-packages/pkg_resources.py", line 626, in require
needed = self.resolve(parse_requirements(requirements))
File "/usr/lib/python2.5/site-packages/pkg_resources.py", line 528, in resolve
raise VersionConflict(dist,req) # XXX put more info here
pkg_resources.VersionConflict: (TurboGears 1.0.4.4 (/usr/lib/python2.5/site-packages/TurboGears-1.0.4.4-py2.5.egg), Requirement.parse('TurboGears>=1.0.5'))
Like this?
assert tuple(map(int,module.__version__.split("."))) >= (1,2), "Module not version 1.2.x"
This is wordy, but works pretty well.
Also, look into pip, which provides more advanced functionality.
You should be using setuptools:
It allows you to lock the dependancies of an application, so even if multiple versions of an egg or package exist on a system only the right one will ever be used.
This is a better way of working: Rather than fail if the wrong version of a dependancy is present it is better to ensure that the right version is present.
Setuptools provides an installer which guarantees that everything required to run the application is present at install-time. It also gives you the means to select which of the many versions of a package which may be present on your PC is the one that gets loaded when you issue an import statement.
If you know the exact formatting of the version string a plain comparison will work:
>>> "1.2.2" > "1.2.1"
True
This will only work if each part of the version is in the single digits, though:
>>> "1.2.2" > "1.2.10" # Bug!
True