Repository: numba/numba
Pull request: fix version_info if version can not be determined
The current implementation assumes that the version info can be split
into at least three parts using the period as delimiter. In certain
situations, such as running from a dowloaded release archive, or when
building from a git checkout that does not have any tags, this
assumption does not hold.

Fixes: #6125

<!--

Thanks for wanting to contribute to Numba :)

First, if you need some help or want to chat to the core developers, please
visit https://gitter.im/numba/numba for real time chat or post to the Numba
mailing list https://groups.google.com/a/continuum.io/forum/#!forum/numba-users.

Here's some guidelines to help the review process go smoothly.

0. Please write a description in this text box of the changes that are being
   made.

1. Please ensure that you have written units tests for the changes made/features
   added.

2. If you are closing an issue please use one of the automatic closing words as
   noted here: https://help.github.com/articles/closing-issues-using-keywords/

3. If your pull request is not ready for review but you want to make use of the
   continuous integration testing facilities please label it with `[WIP]` and
   then remove the label when you'd like it to be reviewed.

4. Once review has taken place please do not add features or make changes out of
   the scope of those requested by the reviewer (doing this just add delays as
   already reviewed code ends up having to be re-reviewed/it is hard to tell
   what is new etc!). Further, please do not rebase your branch on master/force
   push/rewrite history, doing any of these causes the context of any comments
   made by reviewers to be lost. If conflicts occur against master they should
   be resolved by merging master into the branch used for making the pull
   request.

Many thanks in advance for your cooperation!

-->


Problem statement:
IndexError in _version_info with build from master 
I get this error with custom build from latest master(today's commits)

Steps to reproduce:
1. uninstall numba and llvmlite
2. download numba-master.zip and unzip to folder of your choosing
3. run: python setup.py install
4. install numba master + llvm 0.33 or 0.34 rc
5. load any script, e.g:
```
import numba as nb
print('test')
```
6. IndexError..

Hints from issue discussion:
Error report:
```
IndexError                                Traceback (most recent call last)
 in 
----> 1 import numba as nb

~\AppData\Local\Programs\Python\Python37\lib\site-packages\numba-0+unknown-py3.7-win-amd64.egg\numba\__init__.py in 
     33     return _version_info(major, minor, patch, short, full, string, tup,
     34                          git_revision)
---> 35 version_info = _gen_v_info()
     36 del _gen_v_info
     37 

~\AppData\Local\Programs\Python\Python37\lib\site-packages\numba-0+unknown-py3.7-win-amd64.egg\numba\__init__.py in _gen_v_info()
     24             return None
     25     major = try_int(parts[0])
---> 26     minor = try_int(parts[1])
     27     patch = try_int(parts[2])
     28     short = (major, minor)

IndexError: list index out of range
```
@rpopovici thanks for reporting this, would you be able to attach a debugger and print the value of `parts`?
Looks like the version is `0+unknown`, which will be causing the issue - it's assumed in `_gen_v_info()` that the version can be split on dots into at least three parts. (IIRC this version number can occur if versioneer couldn't get the info using git).
May I ask, how was Numba installed?
@esc downloaded the zip from github, unzip, run the install script.. I didn't use git clone if that's what you are asking
Updated the steps as well..
@gmarkall the version is indeed 0+unknown
@rpopovici thank you! I can reproduce this on a git checkout, after removing all tags with:

`git tag | xargs git tag -d`

It is correct that `versioneer.py` will traverse the git history to look for the most recent tag and use that to determine the version number (uses `git describe` internally). 

The fix will be to check if the version number can be split and act accordingly if it can't.
I think in this case, a `version_info` of:

`version_info(major=None, minor=None, patch=None, short=(None, None), full=(None, None, None), string='0+unknown', tuple=('0+unknown',), git_revision=None)`

Is probably the best we can do.
There is a hotfix in #6126.

Commit message: refactor version info generation

Move the version info generation out of `numba/__init__.py` so as not
to crowd the namespace. Also, refactor the code in question to make it
easier to test and add some tests for common version numbers found in
Numba.

Patch to review:
diff --git a/numba/__init__.py b/numba/__init__.py
index b54c0a676f0..d31733c5b93 100644
--- a/numba/__init__.py
+++ b/numba/__init__.py
@@ -6,34 +6,15 @@
 import re
 import sys
 import warnings
-from collections import namedtuple
 
 from ._version import get_versions
+from .misc.init_utils import generate_version_info
+
 __version__ = get_versions()['version']
+version_info = generate_version_info(__version__)
 del get_versions
+del generate_version_info
 
-_version_info = namedtuple('version_info',
-                           ('major minor patch short full '
-                            'string tuple git_revision'))
-def _gen_v_info():
-    parts = __version__.split('.')
-    def try_int(x):
-        try:
-            return int(x)
-        except ValueError:
-            return None
-    major = try_int(parts[0])
-    minor = try_int(parts[1])
-    patch = try_int(parts[2])
-    short = (major, minor)
-    full = (major, minor, patch)
-    string = __version__
-    tup = tuple(string.split('.'))
-    git_revision = tup[3] if len(tup) >= 4 else None
-    return _version_info(major, minor, patch, short, full, string, tup,
-                         git_revision)
-version_info = _gen_v_info()
-del _gen_v_info
 
 from numba.core import config
 from numba.testing import _runtests as runtests
diff --git a/numba/misc/init_utils.py b/numba/misc/init_utils.py
new file mode 100644
index 00000000000..9a6cd87638e
--- /dev/null
+++ b/numba/misc/init_utils.py
@@ -0,0 +1,25 @@
+from collections import namedtuple
+
+version_info = namedtuple('version_info',
+                          ('major minor patch short full '
+                           'string tuple git_revision'))
+
+
+def generate_version_info(version):
+    parts = version.split('.')
+
+    def try_int(x):
+        try:
+            return int(x)
+        except ValueError:
+            return None
+    major = try_int(parts[0]) if len(parts) >= 1 else None
+    minor = try_int(parts[1]) if len(parts) >= 2 else None
+    patch = try_int(parts[2]) if len(parts) >= 3 else None
+    short = (major, minor)
+    full = (major, minor, patch)
+    string = version
+    tup = tuple(parts)
+    git_revision = tup[3] if len(tup) >= 4 else None
+    return version_info(major, minor, patch, short, full, string, tup,
+                        git_revision)
diff --git a/numba/tests/test_inti_utils.py b/numba/tests/test_inti_utils.py
new file mode 100644
index 00000000000..cff483c5eef
--- /dev/null
+++ b/numba/tests/test_inti_utils.py
@@ -0,0 +1,42 @@
+import unittest
+
+from numba.tests.support import TestCase
+
+from numba.misc.init_utils import version_info, generate_version_info
+
+
+class TestGenerateVersionInfo(TestCase):
+
+    def test_major_minor_patch(self):
+        expected = version_info(0, 1, 0,
+                                (0, 1), (0, 1, 0),
+                                "0.1.0", ('0', '1', '0'), None)
+        received = generate_version_info("0.1.0")
+        self.assertEqual(received, expected)
+
+    def test_unknown(self):
+        expected = version_info(None, None, None,
+                                (None, None), (None, None, None),
+                                '0+unknown', ('0+unknown',), None)
+        received = generate_version_info('0+unknown')
+        self.assertEqual(received, expected)
+
+    def test_dev(self):
+        expected = version_info(0, 1, None,
+                                (0, 1), (0, 1, None),
+                                '0.1.0dev0', ('0', '1', '0dev0'), None)
+        received = generate_version_info('0.1.0dev0')
+        self.assertEqual(received, expected)
+
+    def test_full_rev(self):
+        expected = version_info(0, 1, None,
+                                (0, 1), (0, 1, None),
+                                '0.1.0dev0+1.g0123456789abcdef',
+                                ('0', '1', '0dev0+1', 'g0123456789abcdef'),
+                                'g0123456789abcdef')
+        received = generate_version_info('0.1.0dev0+1.g0123456789abcdef')
+        self.assertEqual(received, expected)
+
+
+if __name__ == '__main__':
+    unittest.main()


Code review comments:
diff_hunk: @@ -6,34 +6,15 @@
 import re
 import sys
 import warnings
-from collections import namedtuple
 
 from ._version import get_versions
+from .misc.init_utils import generate_version_info
path: numba/__init__.py
text: @user1:
```suggestion
from numba.misc.init_utils import generate_version_info
```