Skip to content

Commit 0dad360

Browse files
committed
Merge pull request #825 from satra/fix/setup
fix: install to work with pip
2 parents 3c6f389 + e8403dd commit 0dad360

File tree

3 files changed

+263
-32
lines changed

3 files changed

+263
-32
lines changed

nipype/info.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,8 @@ def get_nipype_gitversion():
104104
NUMPY_MIN_VERSION = '1.3'
105105
SCIPY_MIN_VERSION = '0.7'
106106
TRAITS_MIN_VERSION = '4.0'
107+
DATEUTIL_MIN_VERSION = '1.0'
108+
NOSE_MIN_VERSION = '1.0'
107109

108110
NAME = 'nipype'
109111
MAINTAINER = "nipype developers"
@@ -122,6 +124,7 @@ def get_nipype_gitversion():
122124
MICRO = _version_micro
123125
ISRELEASE = _version_extra == ''
124126
VERSION = __version__
125-
REQUIRES = ["nibabel (>=1.0)", "networkx (>=1.0)", "numpy (>=1.3)",
126-
"scipy (>=0.7)", "traits (>=4.0)"]
127+
REQUIRES = ["nibabel>=1.0", "networkx>=1.0", "numpy>=1.3",
128+
"python-dateutil>1.0", "scipy>=0.7", "traits>=4.0",
129+
"nose>=1.0"]
127130
STATUS = 'stable'

requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,6 @@ numpy>=1.3
22
scipy>=0.7
33
networkx>=1.0
44
traits>=4.0
5-
dateutil>=1.5
5+
python-dateutil>=1.5
66
nibabel>=1.0
77
nose>=1.0

setup.py

Lines changed: 257 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -7,17 +7,245 @@
77
packages and create an API for specifying a full analysis pipeline in
88
python.
99
10+
Much of the machinery at the beginning of this file has been copied over from
11+
nibabel denoted by ## START - COPIED FROM NIBABEL and a corresponding ## END
12+
1013
"""
1114

1215
import sys
1316
from glob import glob
17+
import os
18+
19+
## START - COPIED FROM NIBABEL
20+
from os.path import join as pjoin
21+
from functools import partial
1422

15-
# Import build helpers
23+
PY3 = sys.version_info[0] >= 3
24+
if PY3:
25+
string_types = str,
26+
else:
27+
string_types = basestring,
1628
try:
17-
from nisext.sexts import package_check, get_comrec_build
29+
from ConfigParser import ConfigParser
1830
except ImportError:
19-
raise RuntimeError('Need nisext package from nibabel installation'
20-
' - please install nibabel first')
31+
from configparser import ConfigParser
32+
33+
# BEFORE importing distutils, remove MANIFEST. distutils doesn't properly
34+
# update it when the contents of directories change.
35+
if os.path.exists('MANIFEST'): os.remove('MANIFEST')
36+
37+
# For some commands, use setuptools.
38+
if len(set(('develop', 'bdist_egg', 'bdist_rpm', 'bdist', 'bdist_dumb',
39+
'install_egg_info', 'egg_info', 'easy_install', 'bdist_wheel',
40+
'bdist_mpkg')).intersection(sys.argv)) > 0:
41+
# setup_egg imports setuptools setup, thus monkeypatching distutils.
42+
import setup_egg
43+
44+
from distutils.core import setup
45+
46+
from distutils.version import LooseVersion
47+
from distutils.command.build_py import build_py
48+
49+
from distutils import log
50+
51+
def get_comrec_build(pkg_dir, build_cmd=build_py):
52+
""" Return extended build command class for recording commit
53+
54+
The extended command tries to run git to find the current commit, getting
55+
the empty string if it fails. It then writes the commit hash into a file
56+
in the `pkg_dir` path, named ``COMMIT_INFO.txt``.
57+
58+
In due course this information can be used by the package after it is
59+
installed, to tell you what commit it was installed from if known.
60+
61+
To make use of this system, you need a package with a COMMIT_INFO.txt file -
62+
e.g. ``myproject/COMMIT_INFO.txt`` - that might well look like this::
63+
64+
# This is an ini file that may contain information about the code state
65+
[commit hash]
66+
# The line below may contain a valid hash if it has been substituted
67+
during 'git archive' archive_subst_hash=$Format:%h$
68+
# This line may be modified by the install process
69+
install_hash=
70+
71+
The COMMIT_INFO file above is also designed to be used with git substitution
72+
- so you probably also want a ``.gitattributes`` file in the root directory
73+
of your working tree that contains something like this::
74+
75+
myproject/COMMIT_INFO.txt export-subst
76+
77+
That will cause the ``COMMIT_INFO.txt`` file to get filled in by ``git
78+
archive`` - useful in case someone makes such an archive - for example with
79+
via the github 'download source' button.
80+
81+
Although all the above will work as is, you might consider having something
82+
like a ``get_info()`` function in your package to display the commit
83+
information at the terminal. See the ``pkg_info.py`` module in the nipy
84+
package for an example.
85+
"""
86+
class MyBuildPy(build_cmd):
87+
''' Subclass to write commit data into installation tree '''
88+
def run(self):
89+
build_cmd.run(self)
90+
import subprocess
91+
proc = subprocess.Popen('git rev-parse HEAD',
92+
stdout=subprocess.PIPE,
93+
stderr=subprocess.PIPE,
94+
shell=True)
95+
repo_commit, _ = proc.communicate()
96+
# Fix for python 3
97+
repo_commit = str(repo_commit)
98+
# We write the installation commit even if it's empty
99+
cfg_parser = ConfigParser()
100+
cfg_parser.read(pjoin(pkg_dir, 'COMMIT_INFO.txt'))
101+
cfg_parser.set('commit hash', 'install_hash', repo_commit)
102+
out_pth = pjoin(self.build_lib, pkg_dir, 'COMMIT_INFO.txt')
103+
cfg_parser.write(open(out_pth, 'wt'))
104+
return MyBuildPy
105+
106+
def _add_append_key(in_dict, key, value):
107+
""" Helper for appending dependencies to setuptools args """
108+
# If in_dict[key] does not exist, create it
109+
# If in_dict[key] is a string, make it len 1 list of strings
110+
# Append value to in_dict[key] list
111+
if key not in in_dict:
112+
in_dict[key] = []
113+
elif isinstance(in_dict[key], string_types):
114+
in_dict[key] = [in_dict[key]]
115+
in_dict[key].append(value)
116+
117+
# Dependency checks
118+
def package_check(pkg_name, version=None,
119+
optional=False,
120+
checker=LooseVersion,
121+
version_getter=None,
122+
messages=None,
123+
setuptools_args=None,
124+
pypi_pkg_name=None
125+
):
126+
''' Check if package `pkg_name` is present and has good enough version
127+
128+
Has two modes of operation. If `setuptools_args` is None (the default),
129+
raise an error for missing non-optional dependencies and log warnings for
130+
missing optional dependencies. If `setuptools_args` is a dict, then fill
131+
``install_requires`` key value with any missing non-optional dependencies,
132+
and the ``extras_requires`` key value with optional dependencies.
133+
134+
This allows us to work with and without setuptools. It also means we can
135+
check for packages that have not been installed with setuptools to avoid
136+
installing them again.
137+
138+
Parameters
139+
----------
140+
pkg_name : str
141+
name of package as imported into python
142+
version : {None, str}, optional
143+
minimum version of the package that we require. If None, we don't
144+
check the version. Default is None
145+
optional : bool or str, optional
146+
If ``bool(optional)`` is False, raise error for absent package or wrong
147+
version; otherwise warn. If ``setuptools_args`` is not None, and
148+
``bool(optional)`` is not False, then `optional` should be a string
149+
giving the feature name for the ``extras_require`` argument to setup.
150+
checker : callable, optional
151+
callable with which to return comparable thing from version
152+
string. Default is ``distutils.version.LooseVersion``
153+
version_getter : {None, callable}:
154+
Callable that takes `pkg_name` as argument, and returns the
155+
package version string - as in::
156+
157+
``version = version_getter(pkg_name)``
158+
159+
If None, equivalent to::
160+
161+
mod = __import__(pkg_name); version = mod.__version__``
162+
messages : None or dict, optional
163+
dictionary giving output messages
164+
setuptools_args : None or dict
165+
If None, raise errors / warnings for missing non-optional / optional
166+
dependencies. If dict fill key values ``install_requires`` and
167+
``extras_require`` for non-optional and optional dependencies.
168+
pypi_pkg_name : None or string
169+
When the pypi package name differs from the installed module. This is the
170+
case with the package python-dateutil which installs as dateutil.
171+
'''
172+
setuptools_mode = not setuptools_args is None
173+
optional_tf = bool(optional)
174+
if version_getter is None:
175+
def version_getter(pkg_name):
176+
mod = __import__(pkg_name)
177+
return mod.__version__
178+
if messages is None:
179+
messages = {}
180+
msgs = {
181+
'missing': 'Cannot import package "%s" - is it installed?',
182+
'missing opt': 'Missing optional package "%s"',
183+
'opt suffix' : '; you may get run-time errors',
184+
'version too old': 'You have version %s of package "%s"'
185+
' but we need version >= %s', }
186+
msgs.update(messages)
187+
status, have_version = _package_status(pkg_name,
188+
version,
189+
version_getter,
190+
checker)
191+
if pypi_pkg_name:
192+
pkg_name = pypi_pkg_name
193+
194+
if status == 'satisfied':
195+
return
196+
if not setuptools_mode:
197+
if status == 'missing':
198+
if not optional_tf:
199+
raise RuntimeError(msgs['missing'] % pkg_name)
200+
log.warn(msgs['missing opt'] % pkg_name +
201+
msgs['opt suffix'])
202+
return
203+
elif status == 'no-version':
204+
raise RuntimeError('Cannot find version for %s' % pkg_name)
205+
assert status == 'low-version'
206+
if not optional_tf:
207+
raise RuntimeError(msgs['version too old'] % (have_version,
208+
pkg_name,
209+
version))
210+
log.warn(msgs['version too old'] % (have_version,
211+
pkg_name,
212+
version)
213+
+ msgs['opt suffix'])
214+
return
215+
# setuptools mode
216+
if optional_tf and not isinstance(optional, string_types):
217+
raise RuntimeError('Not-False optional arg should be string')
218+
dependency = pkg_name
219+
220+
if version:
221+
dependency += '>=' + version
222+
if optional_tf:
223+
if not 'extras_require' in setuptools_args:
224+
setuptools_args['extras_require'] = {}
225+
_add_append_key(setuptools_args['extras_require'],
226+
optional,
227+
dependency)
228+
return
229+
_add_append_key(setuptools_args, 'install_requires', dependency)
230+
return
231+
232+
233+
def _package_status(pkg_name, version, version_getter, checker):
234+
try:
235+
__import__(pkg_name)
236+
except ImportError:
237+
return 'missing', None
238+
if not version:
239+
return 'satisfied', None
240+
try:
241+
have_version = version_getter(pkg_name)
242+
except AttributeError:
243+
return 'no-version', None
244+
if checker(have_version) < checker(version):
245+
return 'low-version', have_version
246+
return 'satisfied', have_version
247+
248+
## END - COPIED FROM NIBABEL
21249

22250
from build_docs import cmdclass, INFO_VARS
23251

@@ -41,26 +269,30 @@ def configuration(parent_package='',top_path=None):
41269
config.add_subpackage('nipype', 'nipype')
42270
return config
43271

44-
################################################################################
45-
# For some commands, use setuptools
46-
47-
if len(set(('develop', 'bdist_egg', 'bdist_rpm', 'bdist', 'bdist_dumb',
48-
'bdist_wininst', 'install_egg_info', 'egg_info', 'easy_install',
49-
)).intersection(sys.argv)) > 0:
50-
from setup_egg import extra_setuptools_args
51-
52-
# extra_setuptools_args can be defined from the line above, but it can
53-
# also be defined here because setup.py has been exec'ed from
54-
# setup_egg.py.
55-
if not 'extra_setuptools_args' in globals():
56-
extra_setuptools_args = dict()
272+
# Prepare setuptools args
273+
if 'setuptools' in sys.modules:
274+
extra_setuptools_args = dict(
275+
tests_require=['nose'],
276+
test_suite='nose.collector',
277+
zip_safe=False,
278+
extras_require = dict(
279+
doc='Sphinx>=0.3',
280+
test='nose>=0.10.1'),
281+
)
282+
pkg_chk = partial(package_check, setuptools_args = extra_setuptools_args)
283+
else:
284+
extra_setuptools_args = {}
285+
pkg_chk = package_check
57286

58287
# Hard and soft dependency checking
59-
package_check('networkx', INFO_VARS['NETWORKX_MIN_VERSION'])
60-
package_check('nibabel', INFO_VARS['NIBABEL_MIN_VERSION'])
61-
package_check('numpy', INFO_VARS['NUMPY_MIN_VERSION'])
62-
package_check('scipy', INFO_VARS['SCIPY_MIN_VERSION'])
63-
package_check('traits', INFO_VARS['TRAITS_MIN_VERSION'])
288+
pkg_chk('networkx', INFO_VARS['NETWORKX_MIN_VERSION'])
289+
pkg_chk('nibabel', INFO_VARS['NIBABEL_MIN_VERSION'])
290+
pkg_chk('numpy', INFO_VARS['NUMPY_MIN_VERSION'])
291+
pkg_chk('scipy', INFO_VARS['SCIPY_MIN_VERSION'])
292+
pkg_chk('traits', INFO_VARS['TRAITS_MIN_VERSION'])
293+
pkg_chk('nose', INFO_VARS['NOSE_MIN_VERSION'])
294+
pkg_chk('dateutil', INFO_VARS['DATEUTIL_MIN_VERSION'],
295+
pypi_pkg_name='python-dateutil')
64296

65297
################################################################################
66298
# Import the documentation building classes.
@@ -77,7 +309,6 @@ def configuration(parent_package='',top_path=None):
77309

78310
def main(**extra_args):
79311
from numpy.distutils.core import setup
80-
81312
setup(name=INFO_VARS['NAME'],
82313
maintainer=INFO_VARS['MAINTAINER'],
83314
maintainer_email=INFO_VARS['MAINTAINER_EMAIL'],
@@ -91,13 +322,10 @@ def main(**extra_args):
91322
author_email=INFO_VARS['AUTHOR_EMAIL'],
92323
platforms=INFO_VARS['PLATFORMS'],
93324
version=INFO_VARS['VERSION'],
94-
requires=INFO_VARS['REQUIRES'],
95-
configuration = configuration,
96-
cmdclass = cmdclass,
97-
scripts = glob('bin/*'),
325+
configuration=configuration,
326+
cmdclass=cmdclass,
327+
scripts=glob('bin/*'),
98328
**extra_args)
99329

100-
101-
102330
if __name__ == "__main__":
103331
main(**extra_setuptools_args)

0 commit comments

Comments
 (0)