Completed
Pull Request — master (#867)
by Joe
01:47
created

extras_requires()   A

Complexity

Conditions 1

Size

Total Lines 8

Duplication

Lines 0
Ratio 0 %
Metric Value
cc 1
dl 0
loc 8
rs 9.4286
1
#!/usr/bin/env python
2
#
3
# Copyright 2014 Quantopian, Inc.
4
#
5
# Licensed under the Apache License, Version 2.0 (the "License");
6
# you may not use this file except in compliance with the License.
7
# You may obtain a copy of the License at
8
#
9
#     http://www.apache.org/licenses/LICENSE-2.0
10
#
11
# Unless required by applicable law or agreed to in writing, software
12
# distributed under the License is distributed on an "AS IS" BASIS,
13
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
# See the License for the specific language governing permissions and
15
# limitations under the License.
16
from __future__ import print_function
17
18
from distutils.version import StrictVersion
19
from itertools import starmap
20
from operator import lt, gt, eq, le, ge
21
from os.path import (
22
    abspath,
23
    dirname,
24
    join,
25
)
26
from pkg_resources import resource_filename
27
import re
28
from setuptools import (
29
    Extension,
30
    find_packages,
31
    setup,
32
)
33
import sys
34
35
import versioneer
36
37
38
class LazyCommandClass(dict):
39
    """
40
    Lazy command class that defers operations requiring Cython and numpy until
41
    they've actually been downloaded and installed by setup_requires.
42
    """
43
    def __contains__(self, key):
44
        return (
45
            key == 'build_ext'
46
            or super(LazyCommandClass, self).__contains__(key)
47
        )
48
49
    def __setitem__(self, key, value):
50
        if key == 'build_ext':
51
            raise AssertionError("build_ext overridden!")
52
        super(LazyCommandClass, self).__setitem__(key, value)
53
54
    def __getitem__(self, key):
55
        if key != 'build_ext':
56
            return super(LazyCommandClass, self).__getitem__(key)
57
58
        from Cython.Distutils import build_ext as cython_build_ext
59
60
        class build_ext(cython_build_ext):
61
            """
62
            Custom build_ext command that lazily adds numpy's include_dir to
63
            extensions.
64
            """
65
            def build_extensions(self):
66
                """
67
                Lazily append numpy's include directory to Extension includes.
68
69
                This is done here rather than at module scope because setup.py
70
                may be run before numpy has been installed, in which case
71
                importing numpy and calling `numpy.get_include()` will fail.
72
                """
73
                numpy_incl = resource_filename('numpy', 'core/include')
74
                for ext in self.extensions:
75
                    ext.include_dirs.append(numpy_incl)
76
77
                # This explicitly calls the superclass method rather than the
78
                # usual super() invocation because distutils' build_class, of
79
                # which Cython's build_ext is a subclass, is an old-style class
80
                # in Python 2, which doesn't support `super`.
81
                cython_build_ext.build_extensions(self)
82
        return build_ext
83
84
85
ext_modules = list(starmap(Extension, (
86
    ('zipline.assets._assets', ['zipline/assets/_assets.pyx']),
87
    ('zipline.lib.adjustment', ['zipline/lib/adjustment.pyx']),
88
    ('zipline.lib._float64window', ['zipline/lib/_float64window.pyx']),
89
    ('zipline.lib._int64window', ['zipline/lib/_int64window.pyx']),
90
    ('zipline.lib._uint8window', ['zipline/lib/_uint8window.pyx']),
91
    ('zipline.lib.rank', ['zipline/lib/rank.pyx']),
92
    (
93
        'zipline.data._equities',
94
        ['zipline/data/_equities.pyx'],
95
    ),
96
    (
97
        'zipline.data._adjustments',
98
        ['zipline/data/_adjustments.pyx'],
99
    ),
100
)))
101
102
STR_TO_CMP = {
103
    '<': lt,
104
    '<=': le,
105
    '=': eq,
106
    '==': eq,
107
    '>': gt,
108
    '>=': ge,
109
}
110
111
112
def _filter_requirements(lines_iter):
113
    for line in lines_iter:
114
        line = line.strip()
115
        if not line or line.startswith('#'):
116
            continue
117
118
        # pip install -r understands line with ;python_version<'3.0', but
119
        # whatever happens inside extras_requires doesn't.  Parse the line
120
        # manually and conditionally add it if needed.
121
        if ';' not in line:
122
            yield line
123
            continue
124
125
        requirement, version_spec = line.split(';')
126
        try:
127
            groups = re.match(
128
                "(python_version)([<>=]{1,2})(')([0-9\.]+)(')(.*)",
129
                version_spec,
130
            ).groups()
131
            comp = STR_TO_CMP[groups[1]]
132
            version_spec = StrictVersion(groups[3])
133
        except Exception as e:
134
            # My kingdom for a 'raise from'!
135
            raise AssertionError(
136
                "Couldn't parse requirement line; '%s'\n"
137
                "Error was:\n"
138
                "%r" % (line, e)
139
            )
140
141
        sys_version = '.'.join(list(map(str, sys.version_info[:3])))
142
        if comp(sys_version, version_spec):
143
            yield requirement
144
145
146
REQ_UPPER_BOUNDS = {
147
}
148
149
150
def _with_bounds(req):
151
    try:
152
        req, lower = req.split('==')
153
    except ValueError:
154
        return req
155
    else:
156
        with_bounds = [req, '>=', lower]
157
        upper = REQ_UPPER_BOUNDS.get(req)
158
        if upper:
159
            with_bounds.extend([',', upper])
160
        return ''.join(with_bounds)
161
162
163
def read_requirements(path, strict_bounds):
164
    """
165
    Read a requirements.txt file, expressed as a path relative to Zipline root.
166
167
    Returns requirements with the pinned versions as lower bounds
168
    if `strict_bounds` is falsey.
169
    """
170
    real_path = join(dirname(abspath(__file__)), path)
171
    with open(real_path) as f:
172
        reqs = _filter_requirements(f.readlines())
173
174
        if strict_bounds:
175
            return list(reqs)
176
        else:
177
            return list(map(_with_bounds, reqs))
178
179
180
def install_requires(strict_bounds=False):
181
    return read_requirements('etc/requirements.txt',
182
                             strict_bounds=strict_bounds)
183
184
185
def extras_requires():
186
    dev_reqs = read_requirements('etc/requirements_dev.txt',
187
                                 strict_bounds=True)
188
    talib_reqs = ['TA-Lib==0.4.9']
189
    return {
190
        'dev': dev_reqs,
191
        'talib': talib_reqs,
192
        'all': dev_reqs + talib_reqs,
193
    }
194
195
196
def module_requirements(requirements_path, module_names):
197
    module_names = set(module_names)
198
    found = set()
199
    module_lines = []
200
    parser = re.compile("([^=<>]+)([<=>]{1,2})(.*)")
201
    for line in read_requirements(requirements_path, strict_bounds=False):
202
        match = parser.match(line)
203
        if match is None:
204
            raise AssertionError("Could not parse requirement: '%s'" % line)
205
206
        groups = match.groups()
207
        name = groups[0]
208
        if name in module_names:
209
            found.add(name)
210
            module_lines.append(line)
211
212
    if found != module_names:
213
        raise AssertionError(
214
            "No requirements found for %s." % module_names - found
215
        )
216
    return module_lines
217
218
219
def setup_requires():
220
    if not set(sys.argv) & {'install', 'develop', 'egg_info', 'bdist_wheel'}:
221
        return []
222
223
    required = ('Cython', 'numpy')
224
    return module_requirements('etc/requirements.txt', required)
225
226
227
setup(
228
    name='zipline',
229
    version=versioneer.get_version(),
230
    cmdclass=LazyCommandClass(versioneer.get_cmdclass()),
231
    description='A backtester for financial algorithms.',
232
    author='Quantopian Inc.',
233
    author_email='[email protected]',
234
    packages=find_packages('.', include=['zipline', 'zipline.*']),
235
    ext_modules=ext_modules,
236
    scripts=['scripts/run_algo.py'],
237
    include_package_data=True,
238
    license='Apache 2.0',
239
    classifiers=[
240
        'Development Status :: 4 - Beta',
241
        'License :: OSI Approved :: Apache Software License',
242
        'Natural Language :: English',
243
        'Programming Language :: Python',
244
        'Programming Language :: Python :: 2.7',
245
        'Programming Language :: Python :: 3.3',
246
        'Programming Language :: Python :: 3.4',
247
        'Operating System :: OS Independent',
248
        'Intended Audience :: Science/Research',
249
        'Topic :: Office/Business :: Financial',
250
        'Topic :: Scientific/Engineering :: Information Analysis',
251
        'Topic :: System :: Distributed Computing',
252
    ],
253
    install_requires=install_requires(),
254
    setup_requires=setup_requires(),
255
    extras_require=extras_requires(),
256
    url="http://zipline.io",
257
)
258