76

I would like to see if there is any way of requiring a minimal python version.

I have several python modules that are requiring Python 2.6 due to the new exception handling (as keyword).

It looks that even if I check the python version at the beginning of my script, the code will not run because the interpreter will fail inside the module, throwing an ugly system error instead of telling the user to use a newer python.

sorin
  • 161,544
  • 178
  • 535
  • 806
  • See also http://stackoverflow.com/questions/3035749/how-to-write-a-python-2-6-script-that-gracefully-fails-with-older-python and http://stackoverflow.com/questions/388069/python-graceful-future-feature-future-import – Scott Griffiths Jun 03 '11 at 08:50
  • The answer *you* accepted to http://stackoverflow.com/questions/3035749 does not use new Exception handling. – johnsyweb Jun 03 '11 at 09:31
  • 1
    UPDATE (29 October 2016): Now setuptools and pip support `python_requires` keyword to `setup` function – see my [answer](http://stackoverflow.com/a/40300957/95735). – Piotr Dobrogost Oct 28 '16 at 08:27

9 Answers9

60

You can take advantage of the fact that Python will do the right thing when comparing tuples:

#!/usr/bin/python
import sys
MIN_PYTHON = (2, 6)
if sys.version_info < MIN_PYTHON:
    sys.exit("Python %s.%s or later is required.\n" % MIN_PYTHON)
Arkady
  • 14,305
  • 8
  • 42
  • 46
45

You should not use any Python 2.6 features inside the script itself. Also, you must do your version check before importing any of the modules requiring a new Python version.

E.g. start your script like so:

#!/usr/bin/env python
import sys

if sys.version_info[0] != 2 or sys.version_info[1] < 6:
    print("This script requires Python version 2.6")
    sys.exit(1)

# rest of script, including real initial imports, here
Scott Griffiths
  • 21,438
  • 8
  • 55
  • 85
Walter Mundt
  • 24,753
  • 5
  • 53
  • 61
  • 3
    +1. I added some brackets to your `print` because otherwise as Tim pointed out it would have failed with a `SyntaxError` in Python 3 (tricky business this!) I think this is the most correct way to do it - my method is quicker but a bit of a hack. – Scott Griffiths Jun 03 '11 at 10:37
  • I didn't think it would be necessary for it to work in Python 3. After all, Python 3 is, in general, not source code compatible with Python 2.x. Since it is necessary to distribute separate Python 2.x and 3.x versions of just about anything, you can just leave the check out of the Python 3.x version entirely. – Walter Mundt Jun 06 '11 at 14:31
  • 6
    It would be nice to bow out gracefully even when trying to run the script with _3.x_, but that won't work unless the _entire_ script happens to be_syntactically_ valid 3.x. `print` isn't a good choice for printing the error message, because it prints to _stdout_ by default; the simple solution is to use `sys.exit("This script requires Python 2.x, >= 2.6"`) (as in the accepted answer), which prints to _stderr_ and also exits with code 1. (As an aside: on Windows, when invoking with `pythonw.exe`, you'd have to do additional work for the error to surface.) – mklement0 Aug 15 '15 at 21:44
  • The output should match the logic. If it requires 2.6, then check for exactly 2.6. If it needs >= 2.6 then check for that, but say this as the error. If it allows or disallowed 3.x, then please state as such. (Related: https://news.ycombinator.com/item?id=24753602 -- Windows 95 reported its version as 3.95 instead of 4.00 ... writing lexicographical ordering on tuples is surprisingly unintuitive.) – Jason Doucette Nov 13 '22 at 05:25
24

Starting with version 9.0.0 pip supports Requires-Python field in distribution's metadata which can be written by setuptools starting with version 24-2-0. This feature is available through python_requires keyword argument to setup function.

Example (in setup.py):

setup(
...
   python_requires='>=2.5,<2.7',
...

)

To take advantage of this feature one has to package the project/script first if not already done. This is very easy in typical case and should be done nonetheless as it allows users to easily install, use and uninstall given project/script. Please see Python Packaging User Guide for details.

Piotr Dobrogost
  • 41,292
  • 40
  • 236
  • 366
12
import sys
if sys.hexversion < 0x02060000:
    sys.exit("Python 2.6 or newer is required to run this program.")

import module_requiring_26

Also the cool part about this is that it can be included inside the __init__ file or the module.

sorin
  • 161,544
  • 178
  • 535
  • 806
8

I used to have a more complicated approach for supporting both Python2 and Python3, but I no longer try to support Python2, so now I just use:

import sys
MIN_PYTHON = (3, 7)
assert sys.version_info >= MIN_PYTHON, f"requires Python {'.'.join([str(n) for n in MIN_PYTHON])} or newer"

If the version check fails, you get a traceback with something like:

AssertionError: requires Python 3.7 or newer

at the bottom.

Eric Smith
  • 328
  • 4
  • 10
  • 8
    Older versions doesn't understand [f-strings](https://docs.python.org/3/tutorial/inputoutput.html#), so I prefer the [print-f](https://docs.python.org/3/library/stdtypes.html#printf-style-string-formatting) format `"requires Python %s.%s or newer." % MIN_PYTHON`. – Hans Ginzel Oct 12 '20 at 12:51
  • a python script that uses f-strings will not even compile for python versions lower than 3.6 - thus no code will see execution because a compilation error happens earlier. only a wrapper script might solve it in a programmatically and non-educated user compatible fashion. – Alexander Stohr May 25 '21 at 16:01
  • Sure, using old-style formatting will make the test work with older versions. I didn't do that because I have f-strings throughout the rest of my Python source, so using old-style formatting just for that test still won't allow the file to compile and perform the check. I'd have to completely avoid f-strings, which, if I cared about Python before 3.6, is what I'd do. All of my own testing for Python version requirement is for even newer version features. – Eric Smith Sep 01 '21 at 02:10
3

To complement the existing, helpful answers:

You may want to write scripts that run with both Python 2.x and 3.x, and require a minimum version for each.

For instance, if your code uses the argparse module, you need at least 2.7 (with a 2.x Python) or at least 3.2 (with a 3.x Python).

The following snippet implements such a check; the only thing that needs adapting to a different, but analogous scenario are the MIN_VERSION_PY2=... and MIN_VERSION_PY3=... assignments.

As has been noted: this should be placed at the top of the script, before any other import statements.

import sys

MIN_VERSION_PY2 = (2, 7)    # min. 2.x version as major, minor[, micro] tuple
MIN_VERSION_PY3 = (3, 2)    # min. 3.x version

# This is generic code that uses the tuples defined above.
if (sys.version_info[0] == 2 and sys.version_info < MIN_VERSION_PY2
      or
    sys.version_info[0] == 3 and sys.version_info < MIN_VERSION_PY3):
      sys.exit(
        "ERROR: This script requires Python 2.x >= %s or Python 3.x >= %s;"
        " you're running %s." % (
          '.'.join(map(str, MIN_VERSION_PY2)), 
          '.'.join(map(str, MIN_VERSION_PY3)), 
          '.'.join(map(str, sys.version_info))
        )
      )

If the version requirements aren't met, something like the following message is printed to stderr and the script exits with exit code 1.

This script requires Python 2.x >= 2.7 or Python 3.x >= 3.2; you're running 2.6.2.final.0.

Note: This is a substantially rewritten version of an earlier, needlessly complicated answer, after realizing - thanks to Arkady's helpful answer - that comparison operators such as > can directly be applied to tuples.

mklement0
  • 382,024
  • 64
  • 607
  • 775
1

I'm guessing you have something like:

import module_foo
...
import sys
# check sys.version

but module_foo requires a particular version as well? This being the case, it is perfectly valid to rearrange your code thus:

import sys
# check sys.version
import module_foo

Python does not require that imports, aside from from __future__ import [something] be at the top of your code.

lvc
  • 34,233
  • 10
  • 73
  • 98
  • 1
    "Python does not require that imports [...] be at the top of your code." Well that's true, however PEP 8 says: "Imports are always put at the TOP of the file, just after any module comments and docstrings, and before module globals and constants"; so most code linters will complain if you don't put the imports at the top. Nonetheless AFAIK it's unavoidable in this case. – Martin CR Apr 08 '20 at 11:00
-2

I need to make sure I'm using Python 3.5 (or, eventually, higher). I monkeyed around on my own and then I thought to ask SO - but I've not been impressed with the answers (sorry, y'all ::smile::). Rather than giving up, I came up with the approach below. I've tested various combinations of the min_python and max_python specification tuples and it seems to work nicely:

  • Putting this code into a __init__.py is attractive:

    • Avoids polluting many modules with a redundant version check
    • Placing this at the top of a package hierarchy even more further supports the DRY principal, assuming the entire hierarchy abides by the same Python version contraints
    • Takes advantage of a place (file) where I can use the most portable Python code (e.g. Python 1 ???) for the check logic and still write my real modules in the code version I want
    • If I have other package-init stuff that is not "All Python Versions Ever" compatible, I can shovel it into another module, e.g. __init_p3__.py as shown in the sample's commented-out final line. Don't forget to replace the pkgname place holder with the appropriate package name.
  • If you don't want a min (or max), just set it to = ()

  • If you only care about the major version, just use a "one-ple", e.g. = (3, ) Don't forget the comma, otherwise (3) is just a parenthesized (trivial) expression evaluating to a single int
  • You can specify finer min/max than just one or two version levels, e.g. = (3, 4, 1)
  • There will be only one "Consider running as" suggestion when the max isn't actually greater than the min, either because max is an empty tuple (a "none-ple"?), or has fewer elements.

NOTE: I'm not much of a Windoze programmer, so the text_cmd_min and text_cmd_max values are oriented for *Nix systems. If you fix up the code to work in other environments (e.g. Windoze or some particular *Nix variant), then please post. (Ideally, a single super-smartly code block will suffice for all environments, but I'm happy with my *Nix only solution for now.)

PS: I'm somewhat new to Python, and I don't have an interpreter with version less than 2.7.9.final.0, so it's tuff to test my code for earlier variants. On the other hand, does anyone really care? (That's an actual question I have: In what (real-ish) context would I need to deal with the "Graceful Wrong-Python-Version" problem for interpreters prior to 2.7.9?)

__init__.py

'''Verify the Python Interpreter version is in range required by this package'''

min_python = (3, 5)
max_python = (3, )

import sys

if (sys.version_info[:len(min_python)] < min_python) or (sys.version_info[:len(max_python)] > max_python):

    text_have = '.'.join("%s" % n for n in sys.version_info)
    text_min  = '.'.join("%d" % n for n in min_python) if min_python else None
    text_max  = '.'.join("%d" % n for n in max_python) if max_python else None

    text_cmd_min = 'python' + text_min + '  ' + "  ".join("'%s'" % a for a in sys.argv) if min_python              else None
    text_cmd_max = 'python' + text_max + '  ' + "  ".join("'%s'" % a for a in sys.argv) if max_python > min_python else None

    sys.stderr.write("Using Python version: " + text_have + "\n")

    if min_python:  sys.stderr.write(" - Min required: " + text_min + "\n")
    if max_python:  sys.stderr.write(" - Max allowed : " + text_max + "\n")
    sys.stderr.write("\n")

    sys.stderr.write("Consider running as:\n\n")
    if text_cmd_min:  sys.stderr.write(text_cmd_min + "\n")
    if text_cmd_max:  sys.stderr.write(text_cmd_max + "\n")
    sys.stderr.write("\n")

    sys.exit(9)

# import pkgname.__init_p3__
Stevel
  • 631
  • 5
  • 13
-3

Rather than indexing you could always do this,

import platform
if platform.python_version() not in ('2.6.6'):
    raise RuntimeError('Not right version')
Jakob Bowyer
  • 33,878
  • 8
  • 76
  • 91