Completed
Push — master ( fe66bc...2a24eb )
by Manas
18:11
created

write_requirements()   F

Complexity

Conditions 11

Size

Total Lines 50

Duplication

Lines 0
Ratio 0 %
Metric Value
cc 11
dl 0
loc 50
rs 3.375

How to fix   Complexity   

Complexity

Complex classes like write_requirements() often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
#!/usr/bin/env python
2
# Licensed to the StackStorm, Inc ('StackStorm') under one or more
3
# contributor license agreements.  See the NOTICE file distributed with
4
# this work for additional information regarding copyright ownership.
5
# The ASF licenses this file to You under the Apache License, Version 2.0
6
# (the "License"); you may not use this file except in compliance with
7
# the License.  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
17
"""
18
This script is used to automate generation of requirements.txt for st2 components.
19
20
The idea behind this script is that that each component has it's own requirements
21
"in-requirements.txt" file. in-requirements.txt is an input requirements file -
22
a requirements file with dependencies but WITHOUT any version restrictions.
23
24
In addition to this file, there's also the top-level "fixed-requirements.txt"
25
which pins production versions for the whole st2 stack. During production use
26
(building, packaging, etc) requirements.txt is generated from in-requirements.txt
27
where version of packages are fixed according to fixed-requirements.txt.
28
"""
29
30
import argparse
31
import os
32
import os.path
33
import sys
34
from distutils.version import StrictVersion
35
36
OSCWD = os.path.abspath(os.curdir)
37
GET_PIP = '    curl https://bootstrap.pypa.io/get-pip.py | python'
38
39
try:
40
    import pip
41
    from pip.req import parse_requirements
42
except ImportError:
43
    print 'Download pip:\n', GET_PIP
44
    sys.exit(1)
45
46
47
def parse_args():
48
    parser = argparse.ArgumentParser(description='Tool for requirements.txt generation.')
49
    parser.add_argument('-s', '--source-requirements', nargs='+',
50
                        required=True,
51
                        help='Specify paths to requirements file(s). '
52
                        'In case several requirements files are given their content is merged.')
53
    parser.add_argument('-f', '--fixed-requirements', required=True,
54
                        help='Specify path to fixed-requirements.txt file.')
55
    parser.add_argument('-o', '--output-file', default='requirements.txt',
56
                        help='Specify path to the resulting requirements file.')
57
    parser.add_argument('--skip', default=None,
58
                        help=('Comma delimited list of requirements to not '
59
                              'include in the generated file.'))
60
    if len(sys.argv) < 2:
61
        parser.print_help()
62
        sys.exit(1)
63
    return vars(parser.parse_args())
64
65
66
def check_pip_version():
67
    if StrictVersion(pip.__version__) < StrictVersion('6.1.0'):
68
        print "Upgrade pip, your version `{0}' "\
69
              "is outdated:\n".format(pip.__version__), GET_PIP
70
        sys.exit(1)
71
72
73
def load_requirements(file_path):
74
    return tuple((r for r in parse_requirements(file_path, session=False)))
75
76
77
def locate_file(path, must_exist=False):
78
    if not os.path.isabs(path):
79
        path = os.path.join(OSCWD, path)
80
    if must_exist and not os.path.isfile(path):
81
        print("Error: couldn't locate file `{0}'".format(path))
82
    return path
83
84
85
def merge_source_requirements(sources):
86
    """
87
    Read requirements source files and merge it's content.
88
    """
89
    projects = set()
90
    merged_requirements = []
91
    for infile_path in (locate_file(p, must_exist=True) for p in sources):
92
        for req in load_requirements(infile_path):
93
            # Requirements starting with project name "project ..."
94
            if req.req:
95
                # Skip already added project name
96
                if req.req.project_name in projects:
97
                    continue
98
                projects.add(req.req.project_name)
99
                merged_requirements.append(req)
100
101
            # Requirements lines like "vcs+proto://url"
102
            elif req.link:
103
                merged_requirements.append(req)
104
            else:
105
                raise RuntimeError('Unexpected requirement {0}'.format(req))
106
107
    return merged_requirements
108
109
110
def write_requirements(sources=None, fixed_requirements=None, output_file=None,
111
                       skip=None):
112
    """
113
    Write resulting requirements taking versions from the fixed_requirements.
114
    """
115
    skip = skip or []
116
117
    requirements = merge_source_requirements(sources)
118
    fixed = load_requirements(locate_file(fixed_requirements, must_exist=True))
119
120
    # Make sure there are no duplicate / conflicting definitions
121
    fixedreq_hash = {}
122
    for req in fixed:
123
        project_name = req.req.project_name
124
125
        if not req.req:
126
            continue
127
128
        if project_name in fixedreq_hash:
129
            raise ValueError('Duplicate definition for dependency "%s"' % (project_name))
130
131
        fixedreq_hash[project_name] = req
132
133
    lines_to_write = []
134
    links = set()
135
    for req in requirements:
136
        if req.req.project_name in skip:
137
            continue
138
139
        # we don't have any idea how to process links, so just add them
140
        if req.link and req.link not in links:
141
            links.add(req.link)
142
            rline = str(req.link)
143
        elif req.req:
144
            project = req.req.project_name
145
            if project in fixedreq_hash:
146
                rline = str(fixedreq_hash[project].req)
147
            else:
148
                rline = str(req.req)
149
150
        lines_to_write.append(rline)
151
152
    # Sort the lines to guarantee a stable order
153
    lines_to_write = sorted(lines_to_write)
154
    data = '\n'.join(lines_to_write) + '\n'
155
    with open(output_file, 'w') as fp:
156
        fp.write('# Don\'t edit this file. It\'s generated automatically!\n')
157
        fp.write(data)
158
159
    print('Requirements written to: {0}'.format(output_file))
160
161
162
if __name__ == '__main__':
163
    check_pip_version()
164
    args = parse_args()
165
166
    if args['skip']:
167
        skip = args['skip'].split(',')
168
    else:
169
        skip = None
170
171
    write_requirements(sources=args['source_requirements'],
172
                       fixed_requirements=args['fixed_requirements'],
173
                       output_file=args['output_file'],
174
                       skip=skip)
175