2

I'm semi-new to setuptools in python. I recently added a dependency to my project and encountered an issue with the dependency. Here's the problem:

try:
    from setuptools import setup
except ImportError:
    from distutils.core import setup

from mypackage import VERSION

setup(
    name='mypackage',
    ...
    version=VERSION,
    packages=['mypackage'],
    install_requires=['six'])

The problem is that mypackage imports six and thus setup.py fails on fresh installs (six isn't already installed) due to the from mypackage import VERSION line. I have solved the problem by hacking in a dummy module import (seen below), but I really hope there is a better way that doesn't require me to maintain the version number in two locations or a separate file.

try:
    import six
except ImportError:
    # HACK so we can import the VERSION without needing six first   
    import sys
    class HackObj(object):
        def __call__(*args):
            return HackObj()
        def __getattr__(*args):
            return HackObj()
    sys.modules['six'] = HackObj()
    sys.modules['six.moves'] = HackObj()
bboe
  • 4,092
  • 3
  • 29
  • 39

4 Answers4

2

As a general rule, DO NOT import your package from setup.py, since (as you realized) it makes installation of your package impossible if you don't already have all the dependencies installed.

Instead, as suggested in this answer, create a separate module version.py (or similar) without any external dependencies defining VERSION. In your setup.py do execfile('mypackage/version.py') and voila you have access to VERSION without any nasty or unsafe hacks.

As medmunds points out execfile is not available in Python 3, so instead use:

with open('mypackage/version.py') as f:
    exec(f.read())
Community
  • 1
  • 1
kynan
  • 13,235
  • 6
  • 79
  • 81
  • I forgot I posted this question. That other thread has some great suggestions, one of which is similar to how I now do it. Adding my solution soon. – bboe Dec 08 '12 at 03:52
  • Was really happy with the `execfile` solution, but it doesn't work on Python 3. – medmunds Mar 05 '13 at 04:48
  • OK, for Python 2 *and* 3 use `with open('mypackage/version.py') as f: exec(f.read())` in place of `execfile('mypackage/version.py')`. (From http://stackoverflow.com/a/437857/647002) – medmunds Mar 05 '13 at 05:19
1

I am posting the solution that I have been using since many months after asking this question. Thanks to kynan for indirectly prompting me to provide an answer to this question. While the solution kynan posted is great, I personally don't like having to add a single file just for a version number as it means the version number would be saved in the program under package.version.version. PEP 396 indicates that version numbers should be at the top-level of a package's or module's namespace under the name __version__.

The solution I now use uses a simple regex to parse the version number from the line in the python program that defines __version__. It looks like:

import os
import re

PACKAGE_NAME = 'thepackage'
HERE = os.path.abspath(os.path.dirname(__file__))
INIT = open(os.path.join(HERE, PACKAGE_NAME, '__init__.py')).read()
README = open(os.path.join(HERE, 'README.md')).read()

VERSION = re.search("__version__ = '([^']+)'", INIT).group(1)

The process is similar for modules, replacing

INIT = open(os.path.join(HERE, PACKAGE_NAME, '__init__.py')).read()

with

INIT = open(os.path.join(HERE, '{0}.py'.format(PACKAGE_NAME))).read()
bboe
  • 4,092
  • 3
  • 29
  • 39
1

While others have pointed out appropriately that importing your own package from setup.py isn't a very good practice, there are cases where this kind of thing comes up in different ways - where the actual setup process requires some package that won't be available until after the installation of all the dependencies are completed.

In these cases, setuptools provides a soluiton:

from setuptools import setup

setup(
name='mypackage',
...
version=VERSION,
packages=['mypackage'],
setup_requires=['six'],
install_requires=['six'])

This will actually download a local copy of six to be available while the setup process runs. It will also properly install six into your current environment so it is available for use after the setup process completes.

underrun
  • 6,713
  • 2
  • 41
  • 53
0

Couldn't you do that like this?

from pkg_resources import require
version = require("module_name")[0].version

Otherwise you could try to write the version in the init.py but I don't know if that is a good practice, honestly.

luke14free
  • 2,529
  • 1
  • 17
  • 25