zipline.git_versions_from_keywords()   F
last analyzed

Complexity

Conditions 16

Size

Total Lines 43

Duplication

Lines 0
Ratio 0 %
Metric Value
cc 16
dl 0
loc 43
rs 2.7326

How to fix   Complexity   

Complexity

Complex classes like zipline.git_versions_from_keywords() 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
2
# This file helps to compute a version number in source trees obtained from
3
# git-archive tarball (such as those provided by githubs download-from-tag
4
# feature). Distribution tarballs (built by setup.py sdist) and build
5
# directories (produced by setup.py build) will contain a much shorter file
6
# that just contains the computed version number.
7
8
# This file is released into the public domain. Generated by
9
# versioneer-0.15 (https://github.com/warner/python-versioneer)
10
11
import errno
12
import os
13
import re
14
import subprocess
15
import sys
16
17
18
def get_keywords():
19
    # these strings will be replaced by git during git-archive.
20
    # setup.py/versioneer.py will grep for the variable names, so they must
21
    # each be defined on a line of their own. _version.py will just call
22
    # get_keywords().
23
    git_refnames = "$Format:%d$"
24
    git_full = "$Format:%H$"
25
    keywords = {"refnames": git_refnames, "full": git_full}
26
    return keywords
27
28
29
class VersioneerConfig:
30
    pass
31
32
33
def get_config():
34
    # these strings are filled in when 'setup.py versioneer' creates
35
    # _version.py
36
    cfg = VersioneerConfig()
37
    cfg.VCS = "git"
38
    cfg.style = "pep440"
39
    cfg.tag_prefix = ""
40
    cfg.parentdir_prefix = "zipline-"
41
    cfg.versionfile_source = "zipline/_version.py"
42
    cfg.verbose = False
43
    return cfg
44
45
46
class NotThisMethod(Exception):
47
    pass
48
49
50
LONG_VERSION_PY = {}
51
HANDLERS = {}
52
53
54
def register_vcs_handler(vcs, method):  # decorator
55
    def decorate(f):
56
        if vcs not in HANDLERS:
57
            HANDLERS[vcs] = {}
58
        HANDLERS[vcs][method] = f
59
        return f
60
    return decorate
61
62
63
def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
64
    assert isinstance(commands, list)
65
    p = None
66
    for c in commands:
67
        try:
68
            dispcmd = str([c] + args)
69
            # remember shell=False, so use git.cmd on windows, not just git
70
            p = subprocess.Popen([c] + args, cwd=cwd, stdout=subprocess.PIPE,
71
                                 stderr=(subprocess.PIPE if hide_stderr
72
                                         else None))
73
            break
74
        except EnvironmentError:
75
            e = sys.exc_info()[1]
76
            if e.errno == errno.ENOENT:
77
                continue
78
            if verbose:
79
                print("unable to run %s" % dispcmd)
80
                print(e)
81
            return None
82
    else:
83
        if verbose:
84
            print("unable to find command, tried %s" % (commands,))
85
        return None
86
    stdout = p.communicate()[0].strip()
87
    if sys.version_info[0] >= 3:
88
        stdout = stdout.decode()
89
    if p.returncode != 0:
90
        if verbose:
91
            print("unable to run %s (error)" % dispcmd)
92
        return None
93
    return stdout
94
95
96
def versions_from_parentdir(parentdir_prefix, root, verbose):
97
    # Source tarballs conventionally unpack into a directory that includes
98
    # both the project name and a version string.
99
    dirname = os.path.basename(root)
100
    if not dirname.startswith(parentdir_prefix):
101
        if verbose:
102
            print("guessing rootdir is '%s', but '%s' doesn't start with "
103
                  "prefix '%s'" % (root, dirname, parentdir_prefix))
104
        raise NotThisMethod("rootdir doesn't start with parentdir_prefix")
105
    return {"version": dirname[len(parentdir_prefix):],
106
            "full-revisionid": None,
107
            "dirty": False, "error": None}
108
109
110
@register_vcs_handler("git", "get_keywords")
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
111
def git_get_keywords(versionfile_abs):
112
    # the code embedded in _version.py can just fetch the value of these
113
    # keywords. When used from setup.py, we don't want to import _version.py,
114
    # so we do it with a regexp instead. This function is not used from
115
    # _version.py.
116
    keywords = {}
117
    try:
118
        f = open(versionfile_abs, "r")
119
        for line in f.readlines():
120
            if line.strip().startswith("git_refnames ="):
121
                mo = re.search(r'=\s*"(.*)"', line)
122
                if mo:
123
                    keywords["refnames"] = mo.group(1)
124
            if line.strip().startswith("git_full ="):
125
                mo = re.search(r'=\s*"(.*)"', line)
126
                if mo:
127
                    keywords["full"] = mo.group(1)
128
        f.close()
129
    except EnvironmentError:
130
        pass
131
    return keywords
132
133
134
@register_vcs_handler("git", "keywords")
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
135
def git_versions_from_keywords(keywords, tag_prefix, verbose):
136
    if not keywords:
137
        raise NotThisMethod("no keywords at all, weird")
138
    refnames = keywords["refnames"].strip()
139
    if refnames.startswith("$Format"):
140
        if verbose:
141
            print("keywords are unexpanded, not using")
142
        raise NotThisMethod("unexpanded keywords, not a git-archive tarball")
143
    refs = set([r.strip() for r in refnames.strip("()").split(",")])
144
    # starting in git-1.8.3, tags are listed as "tag: foo-1.0" instead of
145
    # just "foo-1.0". If we see a "tag: " prefix, prefer those.
146
    TAG = "tag: "
147
    tags = set([r[len(TAG):] for r in refs if r.startswith(TAG)])
148
    if not tags:
149
        # Either we're using git < 1.8.3, or there really are no tags. We use
150
        # a heuristic: assume all version tags have a digit. The old git %d
151
        # expansion behaves like git log --decorate=short and strips out the
152
        # refs/heads/ and refs/tags/ prefixes that would let us distinguish
153
        # between branches and tags. By ignoring refnames without digits, we
154
        # filter out many common branch names like "release" and
155
        # "stabilization", as well as "HEAD" and "master".
156
        tags = set([r for r in refs if re.search(r'\d', r)])
157
        if verbose:
158
            print("discarding '%s', no digits" % ",".join(refs-tags))
159
    if verbose:
160
        print("likely tags: %s" % ",".join(sorted(tags)))
161
    for ref in sorted(tags):
162
        # sorting will prefer e.g. "2.0" over "2.0rc1"
163
        if ref.startswith(tag_prefix):
164
            r = ref[len(tag_prefix):]
165
            if verbose:
166
                print("picking %s" % r)
167
            return {"version": r,
168
                    "full-revisionid": keywords["full"].strip(),
169
                    "dirty": False, "error": None
170
                    }
171
    # no suitable tags, so version is "0+unknown", but full hex is still there
172
    if verbose:
173
        print("no suitable tags, using unknown + full revision id")
174
    return {"version": "0+unknown",
175
            "full-revisionid": keywords["full"].strip(),
176
            "dirty": False, "error": "no suitable tags"}
177
178
179
@register_vcs_handler("git", "pieces_from_vcs")
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
180
def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command):
181
    # this runs 'git' from the root of the source tree. This only gets called
182
    # if the git-archive 'subst' keywords were *not* expanded, and
183
    # _version.py hasn't already been rewritten with a short version string,
184
    # meaning we're inside a checked out source tree.
185
186
    if not os.path.exists(os.path.join(root, ".git")):
187
        if verbose:
188
            print("no .git in %s" % root)
189
        raise NotThisMethod("no .git directory")
190
191
    GITS = ["git"]
192
    if sys.platform == "win32":
193
        GITS = ["git.cmd", "git.exe"]
194
    # if there is a tag, this yields TAG-NUM-gHEX[-dirty]
195
    # if there are no tags, this yields HEX[-dirty] (no NUM)
196
    describe_out = run_command(GITS, ["describe", "--tags", "--dirty",
197
                                      "--always", "--long"],
198
                               cwd=root)
199
    # --long was added in git-1.5.5
200
    if describe_out is None:
201
        raise NotThisMethod("'git describe' failed")
202
    describe_out = describe_out.strip()
203
    full_out = run_command(GITS, ["rev-parse", "HEAD"], cwd=root)
204
    if full_out is None:
205
        raise NotThisMethod("'git rev-parse' failed")
206
    full_out = full_out.strip()
207
208
    pieces = {}
209
    pieces["long"] = full_out
210
    pieces["short"] = full_out[:7]  # maybe improved later
211
    pieces["error"] = None
212
213
    # parse describe_out. It will be like TAG-NUM-gHEX[-dirty] or HEX[-dirty]
214
    # TAG might have hyphens.
215
    git_describe = describe_out
216
217
    # look for -dirty suffix
218
    dirty = git_describe.endswith("-dirty")
219
    pieces["dirty"] = dirty
220
    if dirty:
221
        git_describe = git_describe[:git_describe.rindex("-dirty")]
222
223
    # now we have TAG-NUM-gHEX or HEX
224
225
    if "-" in git_describe:
226
        # TAG-NUM-gHEX
227
        mo = re.search(r'^(.+)-(\d+)-g([0-9a-f]+)$', git_describe)
228
        if not mo:
229
            # unparseable. Maybe git-describe is misbehaving?
230
            pieces["error"] = ("unable to parse git-describe output: '%s'"
231
                               % describe_out)
232
            return pieces
233
234
        # tag
235
        full_tag = mo.group(1)
236
        if not full_tag.startswith(tag_prefix):
237
            if verbose:
238
                fmt = "tag '%s' doesn't start with prefix '%s'"
239
                print(fmt % (full_tag, tag_prefix))
240
            pieces["error"] = ("tag '%s' doesn't start with prefix '%s'"
241
                               % (full_tag, tag_prefix))
242
            return pieces
243
        pieces["closest-tag"] = full_tag[len(tag_prefix):]
244
245
        # distance: number of commits since tag
246
        pieces["distance"] = int(mo.group(2))
247
248
        # commit: short hex revision ID
249
        pieces["short"] = mo.group(3)
250
251
    else:
252
        # HEX: no tags
253
        pieces["closest-tag"] = None
254
        count_out = run_command(GITS, ["rev-list", "HEAD", "--count"],
255
                                cwd=root)
256
        pieces["distance"] = int(count_out)  # total number of commits
257
258
    return pieces
259
260
261
def plus_or_dot(pieces):
262
    if "+" in pieces.get("closest-tag", ""):
263
        return "."
264
    return "+"
265
266
267
def render_pep440(pieces):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
268
    # now build up version string, with post-release "local version
269
    # identifier". Our goal: TAG[+DISTANCE.gHEX[.dirty]] . Note that if you
270
    # get a tagged build and then dirty it, you'll get TAG+0.gHEX.dirty
271
272
    # exceptions:
273
    # 1: no tags. git_describe was just HEX. 0+untagged.DISTANCE.gHEX[.dirty]
274
275
    if pieces["closest-tag"]:
276
        rendered = pieces["closest-tag"]
277
        if pieces["distance"] or pieces["dirty"]:
278
            rendered += plus_or_dot(pieces)
279
            rendered += "%d.g%s" % (pieces["distance"], pieces["short"])
280
            if pieces["dirty"]:
281
                rendered += ".dirty"
282
    else:
283
        # exception #1
284
        rendered = "0+untagged.%d.g%s" % (pieces["distance"],
285
                                          pieces["short"])
286
        if pieces["dirty"]:
287
            rendered += ".dirty"
288
    return rendered
289
290
291
def render_pep440_pre(pieces):
292
    # TAG[.post.devDISTANCE] . No -dirty
293
294
    # exceptions:
295
    # 1: no tags. 0.post.devDISTANCE
296
297
    if pieces["closest-tag"]:
298
        rendered = pieces["closest-tag"]
299
        if pieces["distance"]:
300
            rendered += ".post.dev%d" % pieces["distance"]
301
    else:
302
        # exception #1
303
        rendered = "0.post.dev%d" % pieces["distance"]
304
    return rendered
305
306
307
def render_pep440_post(pieces):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
308
    # TAG[.postDISTANCE[.dev0]+gHEX] . The ".dev0" means dirty. Note that
309
    # .dev0 sorts backwards (a dirty tree will appear "older" than the
310
    # corresponding clean one), but you shouldn't be releasing software with
311
    # -dirty anyways.
312
313
    # exceptions:
314
    # 1: no tags. 0.postDISTANCE[.dev0]
315
316
    if pieces["closest-tag"]:
317
        rendered = pieces["closest-tag"]
318
        if pieces["distance"] or pieces["dirty"]:
319
            rendered += ".post%d" % pieces["distance"]
320
            if pieces["dirty"]:
321
                rendered += ".dev0"
322
            rendered += plus_or_dot(pieces)
323
            rendered += "g%s" % pieces["short"]
324
    else:
325
        # exception #1
326
        rendered = "0.post%d" % pieces["distance"]
327
        if pieces["dirty"]:
328
            rendered += ".dev0"
329
        rendered += "+g%s" % pieces["short"]
330
    return rendered
331
332
333
def render_pep440_old(pieces):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
334
    # TAG[.postDISTANCE[.dev0]] . The ".dev0" means dirty.
335
336
    # exceptions:
337
    # 1: no tags. 0.postDISTANCE[.dev0]
338
339
    if pieces["closest-tag"]:
340
        rendered = pieces["closest-tag"]
341
        if pieces["distance"] or pieces["dirty"]:
342
            rendered += ".post%d" % pieces["distance"]
343
            if pieces["dirty"]:
344
                rendered += ".dev0"
345
    else:
346
        # exception #1
347
        rendered = "0.post%d" % pieces["distance"]
348
        if pieces["dirty"]:
349
            rendered += ".dev0"
350
    return rendered
351
352
353
def render_git_describe(pieces):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
354
    # TAG[-DISTANCE-gHEX][-dirty], like 'git describe --tags --dirty
355
    # --always'
356
357
    # exceptions:
358
    # 1: no tags. HEX[-dirty]  (note: no 'g' prefix)
359
360
    if pieces["closest-tag"]:
361
        rendered = pieces["closest-tag"]
362
        if pieces["distance"]:
363
            rendered += "-%d-g%s" % (pieces["distance"], pieces["short"])
364
    else:
365
        # exception #1
366
        rendered = pieces["short"]
367
    if pieces["dirty"]:
368
        rendered += "-dirty"
369
    return rendered
370
371
372
def render_git_describe_long(pieces):
373
    # TAG-DISTANCE-gHEX[-dirty], like 'git describe --tags --dirty
374
    # --always -long'. The distance/hash is unconditional.
375
376
    # exceptions:
377
    # 1: no tags. HEX[-dirty]  (note: no 'g' prefix)
378
379
    if pieces["closest-tag"]:
380
        rendered = pieces["closest-tag"]
381
        rendered += "-%d-g%s" % (pieces["distance"], pieces["short"])
382
    else:
383
        # exception #1
384
        rendered = pieces["short"]
385
    if pieces["dirty"]:
386
        rendered += "-dirty"
387
    return rendered
388
389
390
def render(pieces, style):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
391
    if pieces["error"]:
392
        return {"version": "unknown",
393
                "full-revisionid": pieces.get("long"),
394
                "dirty": None,
395
                "error": pieces["error"]}
396
397
    if not style or style == "default":
398
        style = "pep440"  # the default
399
400
    if style == "pep440":
401
        rendered = render_pep440(pieces)
402
    elif style == "pep440-pre":
403
        rendered = render_pep440_pre(pieces)
404
    elif style == "pep440-post":
405
        rendered = render_pep440_post(pieces)
406
    elif style == "pep440-old":
407
        rendered = render_pep440_old(pieces)
408
    elif style == "git-describe":
409
        rendered = render_git_describe(pieces)
410
    elif style == "git-describe-long":
411
        rendered = render_git_describe_long(pieces)
412
    else:
413
        raise ValueError("unknown style '%s'" % style)
414
415
    return {"version": rendered, "full-revisionid": pieces["long"],
416
            "dirty": pieces["dirty"], "error": None}
417
418
419
def get_versions():
420
    # I am in _version.py, which lives at ROOT/VERSIONFILE_SOURCE. If we have
421
    # __file__, we can work backwards from there to the root. Some
422
    # py2exe/bbfreeze/non-CPython implementations don't do __file__, in which
423
    # case we can only use expanded keywords.
424
425
    cfg = get_config()
426
    verbose = cfg.verbose
427
428
    try:
429
        return git_versions_from_keywords(get_keywords(), cfg.tag_prefix,
430
                                          verbose)
431
    except NotThisMethod:
432
        pass
433
434
    try:
435
        root = os.path.realpath(__file__)
436
        # versionfile_source is the relative path from the top of the source
437
        # tree (where the .git directory might live) to this file. Invert
438
        # this to find the root from __file__.
439
        for i in cfg.versionfile_source.split('/'):
440
            root = os.path.dirname(root)
441
    except NameError:
442
        return {"version": "0+unknown", "full-revisionid": None,
443
                "dirty": None,
444
                "error": "unable to find root of source tree"}
445
446
    try:
447
        pieces = git_pieces_from_vcs(cfg.tag_prefix, root, verbose)
448
        return render(pieces, cfg.style)
449
    except NotThisMethod:
450
        pass
451
452
    try:
453
        if cfg.parentdir_prefix:
454
            return versions_from_parentdir(cfg.parentdir_prefix, root, verbose)
455
    except NotThisMethod:
456
        pass
457
458
    return {"version": "0+unknown", "full-revisionid": None,
459
            "dirty": None,
460
            "error": "unable to compute version"}
461