Coverage for src/pymyinstall/installhelper/module_install_version.py: 73%
503 statements
« prev ^ index » next coverage.py v7.1.0, created at 2023-07-19 01:47 +0200
« prev ^ index » next coverage.py v7.1.0, created at 2023-07-19 01:47 +0200
1"""
2@file
3@brief Functions to get module version, license, dependencies
4"""
5import sys
6import re
7import warnings
8import functools
9import time
10import platform
11import xmlrpc.client as xmlrpc_client
12from .install_cmd_helper import run_cmd, get_pip_program
13from .module_install_exceptions import (
14 MissingPackageOnPyPiException, AnnoyingPackageException)
15from .module_install_exceptions import (
16 ConfigurationError, MissingVersionOnPyPiException,
17 WrongVersionError, MissingVersionWheelException)
18from .install_cmd_regex import regex_wheel_versions
19from .pip_helper import get_installed_distributions
22annoying_modules = {"pygame", "liblinear", "mlpy", "VideoCapture",
23 "libsvm", "opencv_python", "scikits.cuda",
24 "NLopt"}
27_get_module_version_manual_memoize = {}
30def get_module_version(module, use_cmd=False):
31 """
32 Returns a dictionary ``{ module: version }``.
34 @param module unused, None
35 @param use_cmd use command line
36 @return dictionary
37 """
38 if module is not None:
39 modl = module.lower()
40 res = get_module_version(None, use_cmd=use_cmd)
41 return res.get(modl, None)
43 global _get_module_version_manual_memoize # pylint: disable=W0602
44 if len(_get_module_version_manual_memoize) > 0:
45 return _get_module_version_manual_memoize
47 res = {}
49 if use_cmd:
50 prog = get_pip_program()
51 cmd = prog + " list"
52 out, err = run_cmd(cmd, wait=True, fLOG=None)
54 if err is not None and len(err) > 0:
55 if len(err.split("\n")) > 3 or \
56 "You should consider upgrading via the 'pip install --upgrade pip' command." not in err:
57 raise RuntimeError("unable to run, #lines {0}\nCMD:\n{3}\nERR-J:\n{1}\nOUT:\n{2}".format(
58 len(err.split("\n")), err, out, cmd))
59 lines = out.split("\n")
61 for line in lines:
62 if "(" in line:
63 spl = line.split()
64 if len(spl) == 2:
65 a = spl[0]
66 b = spl[1].strip(" \n\r")
67 res[a] = b.strip("()")
68 al = a.lower()
69 if al != a:
70 res[al] = res[a]
71 else:
72 # local_only must be False to get all modules
73 # not only the ones installed in the virtual environment
74 dist = get_installed_distributions(local_only=False)
75 if len(dist) == 0:
76 raise ConfigurationError("no installed module, unexpected (pip should be there): " +
77 "sys.executable={0}, sys.prefix={1}, sys.base_prefix={2}".format(
78 sys.executable, sys.prefix, sys.base_prefix))
79 for mod in dist:
80 al = mod.key.lower()
81 a = mod.key
82 try:
83 v = mod.version
84 except ValueError:
85 v = "UNKNOWN"
86 res[a] = v
87 if a != al:
88 res[al] = v
90 _get_module_version_manual_memoize.update(res)
91 return res
94def is_installed(name):
95 """
96 Tells if a module is installed or not.
98 @param name module name
99 @return boolean
100 """
101 return get_module_version(name) is not None
104_get_module_metadata_manual_memoize = {}
107def get_module_metadata(module, use_cmd=False, refresh_cache=False):
108 """
109 Returns a dictionary ``{ module: metadata }``.
111 @param module unused, None
112 @param refresh_cache refresh the cache before getting metadata
113 @return dictionary
114 """
115 if module is not None:
116 modl = module.lower()
117 res = get_module_metadata(
118 None, use_cmd=use_cmd, refresh_cache=refresh_cache)
119 return res.get(modl, None)
121 global _get_module_metadata_manual_memoize # pylint: disable=W0602
122 if not refresh_cache and len(_get_module_metadata_manual_memoize) > 0:
123 return _get_module_metadata_manual_memoize
125 # local_only must be False to get all modules
126 # not only the ones installed in the virtual environment
127 dist = get_installed_distributions(local_only=False, use_cmd=use_cmd)
128 if len(dist) == 0:
129 raise ConfigurationError("no installed module, unexpected (pip should be there): " +
130 "sys.executable={0}, sys.prefix={1}, sys.base_prefix={2}".format(
131 sys.executable, sys.prefix, sys.base_prefix))
132 res = {}
133 for mod in dist:
134 d = {}
135 lines = mod._get_metadata(mod.PKG_INFO)
136 for line in lines:
137 if sys.version_info[0] == 2:
138 typstr = str # unicode#
139 if not isinstance(line, typstr):
140 line = typstr(line, encoding="utf8", errors="ignore")
141 try:
142 spl = line.split(":")
143 except UnicodeDecodeError:
144 warnings.warn("UnicodeDecodeError with: " + line)
145 continue
146 key = spl[0].strip()
147 value = ":".join(spl[1:]).strip()
148 if key not in d:
149 d[key] = value
150 else:
151 if not isinstance(d[key], list):
152 d[key] = [d[key]]
153 d[key].append(value)
155 a = mod.key
156 res[a] = d
157 al = mod.key.lower()
158 if a != al:
159 res[al] = d
161 _get_module_metadata_manual_memoize.update(res)
162 return res
165def _get_pypi_version_memoize_op(f):
166 memo = {}
168 def helper(module_name, full_list=False, url="https://pypi.python.org/pypi"):
169 key = module_name, full_list, url
170 if key not in memo:
171 memo[key] = f(module_name, full_list, url)
172 return memo[key]
173 return helper
176_get_pypi_version_memoize = {}
179def get_pypi_version(module_name, full_list=False, url="https://pypi.python.org/pypi", skip_betas=True):
180 """
181 Returns the version of a package on :epkg:`pypi`,
182 we skip alpha, beta or dev version.
184 @param module_name module name
185 @param url pypi server
186 @param full_list results as a list or return the last stable version
187 @param skip_betas skip the intermediate functions
188 @return version (str or list)
190 See also `installing_python_packages_programatically.py <https://gist.github.com/rwilcox/755524>`_,
191 `pkgtools.pypi: PyPI interface <http://pkgtools.readthedocs.org/en/latest/pypi.html>`_.
193 It the function fails, check the status of
194 `Python Infrastructure <https://status.python.org/>`_.
195 It can return errors::
197 ProtocolError: ProtocolError for pypi.python.org/pypi: 503 No healthy backends
198 """
200 global _get_pypi_version_memoize # pylint: disable=W0602
201 key = module_name, full_list, url
202 if key in _get_pypi_version_memoize:
203 available = _get_pypi_version_memoize[key]
204 if full_list:
205 return available
206 elif available is not None and len(available) > 0:
207 return available[0]
208 return None
210 def pypi_package_releases(module_name, b):
211 nbtry = 0
212 while nbtry < 40:
213 try:
214 available = pypi.package_releases(module_name, True)
215 return available
216 except Exception as e:
217 if ("HTTPTooManyRequests" in str(type(e)) or
218 "HTTPTooManyRequests" in str(e)):
219 nbtry += 1
220 warnings.warn(str(e))
221 time.sleep(90)
222 continue
223 if ("TimeoutError" in str(type(e)) or
224 "TimeoutError" in str(e)):
225 nbtry += 1
226 warnings.warn(str(e))
227 time.sleep(20)
228 continue
229 raise e
230 return None
232 def _inside_loop_(pypi, module_name, tried):
234 available = pypi_package_releases(module_name, True)
236 if available is None or len(available) == 0:
237 ntry = module_name.capitalize()
238 if ntry not in tried:
239 tried.append(ntry)
240 available = pypi_package_releases(tried[-1], True)
242 if available is None or len(available) == 0:
243 ntry = module_name.replace("-", "_")
244 if ntry not in tried:
245 tried.append(ntry)
246 available = pypi_package_releases(tried[-1], True)
248 if available is None or len(available) == 0:
249 ntry = module_name.replace("_", "-")
250 if ntry not in tried:
251 tried.append(ntry)
252 available = pypi_package_releases(tried[-1], True)
254 if available is None or len(available) == 0:
255 ntry = module_name.lower()
256 if ntry not in tried:
257 tried.append(ntry)
258 available = pypi_package_releases(tried[-1], True)
260 if available is None or len(available) == 0:
261 ml = module_name.lower()
262 if ml == "markupsafe":
263 tried.append("MarkupSafe")
264 available = pypi_package_releases(tried[-1], True)
265 elif ml == "flask-sqlalchemy":
266 tried.append("Flask-SQLAlchemy")
267 available = pypi_package_releases(tried[-1], True)
268 elif ml == "apscheduler":
269 tried.append("APScheduler")
270 available = pypi_package_releases(tried[-1], True)
271 elif ml == "datashape":
272 tried.append("DataShape")
273 available = pypi_package_releases(tried[-1], True)
274 elif ml == "pycontracts":
275 tried.append("PyContracts")
276 available = pypi_package_releases(tried[-1], True)
277 elif ml == "pybrain":
278 tried.append("PyBrain")
279 available = pypi_package_releases(tried[-1], True)
280 elif ml == "pyexecjs":
281 tried.append("PyExecJS")
282 available = pypi_package_releases(tried[-1], True)
283 elif ml == "dataspyre":
284 tried.append("DataSpyre")
285 available = pypi_package_releases(tried[-1], True)
286 elif ml == "heapdict":
287 tried.append("HeapDict")
288 available = pypi_package_releases(tried[-1], True)
289 elif ml == "pyreact":
290 tried.append("PyReact")
291 available = pypi_package_releases(tried[-1], True)
292 elif ml == "qtpy":
293 tried.append("QtPy")
294 available = pypi_package_releases(tried[-1], True)
295 elif ml == "pythonqwt":
296 tried.append("PythonQwt")
297 available = pypi_package_releases(tried[-1], True)
298 elif ml == "onedrive-sdk-python":
299 tried.append("onedrivesdk")
300 available = pypi_package_releases(tried[-1], True)
301 elif ml.startswith("orange3-"):
302 s = ml.split("-")[1]
303 ntry = "Orange3-" + s[0].upper() + s[1:]
304 tried.append(ntry)
305 available = pypi_package_releases(tried[-1], True)
306 elif module_name in annoying_modules:
307 raise AnnoyingPackageException(module_name)
309 # this raises a warning about an opened connection
310 # see documentation of the function
311 # del pypi
313 return available
315 tried = [module_name]
317 if sys.version_info[:2] <= (3, 4):
318 # the client does not an implemented of __exit__ for version <= 3.4
319 pypi = xmlrpc_client.ServerProxy(url)
320 available = _inside_loop_(pypi, module_name, tried)
321 else:
322 with xmlrpc_client.ServerProxy(url) as pypi:
323 available = _inside_loop_(pypi, module_name, tried)
325 if available is None or len(available) == 0:
326 raise MissingPackageOnPyPiException("tried:\n" + "\n".join(tried))
328 def filter_betas(a):
329 spl = a.split(".")
330 if len(spl) in (2, 3):
331 last = spl[-1]
332 if not skip_betas or ("a" not in last and "b" not in last and "dev" not in last):
333 return True
334 else:
335 # we don't really know here, so we assume it is not
336 return True
337 return False
339 if available:
340 available2 = list(filter(filter_betas, available))
341 if available2:
342 _get_pypi_version_memoize[key] = available2
343 if full_list:
344 return available2
345 return available2[0]
347 raise MissingVersionOnPyPiException(
348 "{0}\nversion:\n{1}".format(module_name, "\n".join(available)))
351def numeric_version(vers):
352 """
353 convert a string into a tuple with numbers wherever possible
355 @param vers string
356 @return tuple
357 """
358 if isinstance(vers, tuple):
359 return vers
360 if isinstance(vers, list):
361 raise RuntimeError("unexpected value:" + str(vers))
362 spl = str(vers).split(".")
363 r = []
364 for _ in spl:
365 try:
366 i = int(_)
367 r.append(i)
368 except ValueError:
369 r.append(_)
370 return tuple(r)
373def compare_version(num, vers):
374 """
375 Compares two versions.
377 @param num first version
378 @param vers second version
379 @return -1, 0, 1
380 """
381 if num is None:
382 if vers is None:
383 return 0
384 return 1
385 if vers is None:
386 return -1
388 if not isinstance(vers, tuple):
389 vers = numeric_version(vers)
390 if not isinstance(num, tuple):
391 num = numeric_version(num)
393 if len(num) == len(vers):
394 for a, b in zip(num, vers):
395 if isinstance(a, int) and isinstance(b, int):
396 if a < b:
397 return -1
398 if a > b:
399 return 1
400 else:
401 a = str(a)
402 b = str(b)
403 if a < b:
404 return -1
405 if a > b:
406 return 1
407 return 0
409 if len(num) < len(vers):
410 num = num + (0,) * (len(vers) - len(num))
411 return compare_version(num, vers)
412 vers = vers + (0,) * (len(num) - len(vers))
413 return compare_version(num, vers)
416def version_consensus(v1, v2):
417 """
418 *v1* and *v2* are two versions of the same module, which one to keep?
420 @param v1 version 1
421 @param v2 version 2
422 @return consensus
424 * ``v1=None``, ``v2='(>=1.5)'`` --> ``v='>=1.5'``
426 To improve:
428 * ``v1='<=1.6'``, ``v2='(>=1.5)'`` --> ``v='==1.6'``
429 """
430 reg = re.compile("([><=!]*)([^><=!]+)")
432 def process_version(v):
433 if isinstance(v, str # unicode#
434 ):
435 v = v.strip('()')
436 find = reg.search(v)
437 if not find:
438 raise WrongVersionError(v)
439 sign = find.groups()[0]
440 number = numeric_version(find.groups()[1])
441 else:
442 try:
443 sign, number = v
444 except ValueError as e:
445 raise ValueError("weird format: " + str(v) +
446 " - " + str(type(v))) from e
447 return sign, number
449 if v1 is None:
450 return v2
451 if v2 is None:
452 return v1
454 s1, n1 = process_version(v1)
455 s2, n2 = process_version(v2)
457 if s1 not in ('<=', '==', '<', '>', '>=', '!='):
458 raise ValueError("wrong sign '{0}' for v1='{1}'".format(s1, v1))
459 if s2 not in ('<=', '==', '<', '>', '>=', '!='):
460 raise ValueError("wrong sign '{0}' for v1='{1}'".format(s2, v2))
462 if s1 == "==":
463 if s2 == "==":
464 if compare_version(n1, n2) != 0:
465 raise WrongVersionError(
466 "incompatible version: {0}{1} and {2}{3}".format(s1, n1, s2, n2))
467 res = s1, n1
468 else:
469 res = s1, n1
471 elif s1 == "<=":
472 if s2 == "<=":
473 res = s1, min(n1, n2)
474 elif s2 == "==":
475 if compare_version(n1, n2) < 0:
476 raise WrongVersionError(
477 "incompatible version: {0}{1} and {2}{3}".format(s1, n1, s2, n2))
478 res = s2, n2
479 elif s2 == '<':
480 if compare_version(n1, n2) == -1:
481 res = s1, n1
482 else:
483 res = s2, n2
484 elif s2 in ('>', '>='):
485 if compare_version(n1, n2) <= 0:
486 raise WrongVersionError(
487 "incompatible version: {0}{1} and {2}{3}".format(s1, n1, s2, n2))
488 res = s1, n1
490 elif s1 == "<":
491 if s2 == "<":
492 res = s1, min(n1, n2)
493 elif s2 == "==":
494 if compare_version(n1, n2) <= 0:
495 raise WrongVersionError(
496 "incompatible version: {0}{1} and {2}{3}".format(s1, n1, s2, n2))
497 res = s2, n2
498 elif s2 == '<=':
499 if compare_version(n1, n2) <= 0:
500 res = s1, n1
501 else:
502 res = s2, n2
503 elif s2 in ('>', '>='):
504 if compare_version(n1, n2) <= 0:
505 raise WrongVersionError(
506 "incompatible version: {0}{1} and {2}{3}".format(s1, n1, s2, n2))
507 res = s1, n1
509 elif s1 == ">=":
510 if s2 == ">=":
511 res = s1, max(n1, n2)
512 elif s2 == "==":
513 if compare_version(n1, n2) == -1:
514 raise WrongVersionError(
515 "incompatible version: {0}{1} and {2}{3}".format(s1, n1, s2, n2))
516 res = s2, n2
517 elif s2 == '>':
518 if compare_version(n1, n2) <= 0:
519 res = s2, n2
520 else:
521 res = s1, n1
522 elif s2 in ('<', '<='):
523 if compare_version(n1, n2) >= 0:
524 raise WrongVersionError(
525 "incompatible version: {0}{1} and {2}{3}".format(s1, n1, s2, n2))
526 res = s2, n2
527 elif s2 == '!=':
528 if compare_version(n1, n2) == 0:
529 raise WrongVersionError(
530 "incompatible version: {0}{1} and {2}{3}".format(s1, n1, s2, n2))
531 res = s1, max(n1, n2)
533 elif s1 == ">":
534 if s2 == ">":
535 res = s1, max(n1, n2)
536 elif s2 == "==":
537 if compare_version(n1, n2) >= 0:
538 raise WrongVersionError(
539 "incompatible version: {0}{1} and {2}{3}".format(s1, n1, s2, n2))
540 res = s2, n2
541 elif s2 == '>=':
542 if compare_version(n1, n2) == -1:
543 res = s2, n2
544 else:
545 res = s1, n1
546 elif s2 in ('<', '<='):
547 if compare_version(n1, n2) == 1:
548 raise WrongVersionError(
549 "incompatible version: {0}{1} and {2}{3}".format(s1, n1, s2, n2))
550 res = s2, n2
551 else:
552 res = None, None
554 if res[0] is None:
555 raise WrongVersionError(
556 "incompatible version and wrong format: {0}{1} and {2}{3}".format(s1, n1, s2, n2))
558 return '{0}{1}'.format(res[0], '.'.join(str(_) for _ in res[1]))
561_get_module_dependencies_deps = None
564def get_module_dependencies(module, use_cmd=False, deep=False, collapse=True, use_pip=None, refresh_cache=False):
565 """
566 Returns the dependencies for a module.
568 @param module unused, None
569 @param use_cmd use command line
570 @param deep dig into dependencies of dependencies
571 @param collapse only one row per module
572 @param use_pip use pip to discover dependencies or not (parse metadata)
573 @param refresh_cache refresh the cache (see below)
574 @return list of tuple (module, version, required by as a str)
575 or dictionary { module: (version, required by as a list) } if *collapse* is True
577 The function which uses *use_pip=True* is not fully tested, it does not
578 return contraints (== 2.4). The function caches the results to avoid doing it again
579 during a second execution unless *refresh_cache* is True.
580 This function is not tested on Python 2.7.
581 """
582 if use_pip is None:
583 use_pip = not sys.platform.startswith("win")
585 def evaluate_condition(cond, full):
586 python_version = ".".join(str(_) for _ in sys.version_info[:3])
587 extra = ""
588 sys_platform = sys.platform
589 platform_machine = platform.machine
590 platform_python_implementation = platform.python_implementation
591 try:
592 return eval(cond)
593 except Exception:
594 if "python_version" not in cond and "extra" not in cond:
595 # probably something like cycler (>=0.10)
596 # we don't check that
597 return True
598 mes = ("Unable to evaluate condition '{0}' from '{1}', "
599 "extra='{2}', python_version='{3}', "
600 "sys_platform='{4}' platform_machine='{5}', "
601 "platform_python_implementation='{6}'.").format(
602 cond, full, extra, python_version, sys_platform,
603 platform_machine, platform_python_implementation)
604 raise RuntimeError(mes)
606 if use_pip:
607 global _get_module_dependencies_deps
608 if _get_module_dependencies_deps is None or refresh_cache:
609 temp = get_installed_distributions(
610 local_only=False, skip=[], use_cmd=use_cmd)
611 _get_module_dependencies_deps = dict(
612 (p.key, (p, p.requires())) for p in temp)
613 if module not in _get_module_dependencies_deps:
614 raise ValueError("module {0} was not installed".format(module))
615 res = []
616 req = _get_module_dependencies_deps[module][1]
617 if isinstance(req, list):
618 for r in req:
619 res.append((r.key, None, module))
620 else:
621 res.append((req.key, None, module))
622 else:
623 meta = get_module_metadata(
624 module, use_cmd, refresh_cache=refresh_cache)
625 if meta is None:
626 raise ImportError(
627 "unable to get metadata for module '{0}' - refresh_cache={1}".format(module, refresh_cache))
628 deps = [v for k, v in meta.items()
629 if "Requires" in k and "Requires-Python" not in k]
630 res = []
631 for d in deps:
632 if not isinstance(d, list):
633 dl = [d]
634 else:
635 dl = d
636 for v in dl:
637 spl = v.split()
638 if len(spl) > 1:
639 spl = [spl[0], " ".join(spl[1:])]
640 if len(spl) <= 1:
641 key = (module, None, v)
642 else:
643 conds = spl[1].split(";")
644 ok = [evaluate_condition(cond, v) for cond in conds]
645 if not all(ok):
646 continue
647 key = (spl[0].strip(";"), spl[1], module)
648 if key not in res:
649 res.append(key)
651 # specific filters
652 def validate_module(name):
653 if name == "enum34" and sys.version_info[:2] > (3, ):
654 raise NameError(
655 "Unexpected dependency '{0}' for module '{1}'.".format(name, module))
656 if name == "configparser":
657 raise NameError(
658 "Unexpected dependency '{0}' for module '{1}'.".format(name, module))
659 return True
661 res = [key for key in res if validate_module(key[0])]
663 if deep:
664 done = {module: None}
665 mod = 1
666 while mod > 0:
667 mod = 0
668 for r in res:
669 if r[0] not in done:
670 if r[0].lower() < 'a' or r[0].lower() > 'zzzzzz' or r[0].endswith(";"):
671 raise NameError(
672 "A module has an unexpected name '{0}', r={1} "
673 "when looking for dependencies of '{2}'.".format(
674 r[0], r, module))
675 temp = get_module_dependencies(
676 r[0], use_cmd=use_cmd, deep=deep, collapse=False,
677 use_pip=use_pip, refresh_cache=refresh_cache)
678 for key in temp:
679 if key not in res:
680 res.append(key)
681 mod += 1
682 done[r[0]] = None
684 if collapse:
685 final = {}
686 for name, version, required in res:
687 if name not in final:
688 final[name] = (version, [required])
689 else:
690 ex = final[name][1]
691 if required not in ex:
692 ex.append(required)
693 try:
694 v = version_consensus(final[name][0], version)
695 except WrongVersionError as e:
696 raise WrongVersionError("unable to reconcile versions:\n{0}\n{1}".format(
697 ex, str((name, version, required)))) from e
698 final[name] = (v, ex)
699 final = {k: (v[0], list(sorted(v[1]))) for k, v in final.items()}
700 return final
702 return [(name, version.strip('()') if version is not None else version, required)
703 for name, version, required in res]
706def choose_most_recent(list_name):
707 """
708 Chooses the most recent version for a list of module names.
710 @param list_name list of names
711 @return most recent version or None if the input list is empty
713 In the following case, we would choose the first option::
715 numpy-1.10.0+mkl-cp34-none-win_amd64.whl
716 numpy-1.9.1.0+mkl-cp34-none-win_amd64.whl
717 """
718 def find_wheel(tu):
719 for t in tu:
720 if ".whl" in t:
721 return t
722 raise ValueError("unable to find a wheel in {0}".format(tu))
723 if len(list_name) == 0:
724 return None
725 if isinstance(list_name[0], tuple):
726 list_name = [(find_wheel(_), _) for _ in list_name]
727 else:
728 list_name = [(_, _) for _ in list_name]
730 versions = [re.compile(_) for _ in regex_wheel_versions]
732 def search_regex(_):
733 resv = None
734 for version in versions:
735 try:
736 resv = version.search(_[0])
737 except TypeError as e:
738 raise TypeError("Unable to parse '{0}'".format(_)) from e
739 if resv is not None:
740 return resv
741 raise MissingVersionWheelException(
742 "Unable to get version number for module '{}':\nREGEX\n{}".format(_, "\n".join(regex_wheel_versions)))
744 list_name = [(search_regex(_).groups()[0], _[0], _[1])
745 for _ in list_name]
747 def cmp(el1, el2):
748 return compare_version(el1[0], el2[0])
750 list_name = list(sorted(list_name, key=functools.cmp_to_key(cmp)))
751 return list_name[-1][-1]
754def get_wheel_version(whlname):
755 """
756 extract the version from a wheel file,
757 return ``2.6.0`` for ``rpy2-2.6.0-cp34-none-win_amd64.whl``
759 @param whlname file name
760 @return string
761 """
762 find = []
763 for reg in regex_wheel_versions:
764 if len(find) == 0:
765 exp = re.compile(reg)
766 find = exp.findall(whlname)
767 else:
768 break
769 if len(find) == 0:
770 mes = "Unable to extract version for '{0}'\nREGEX\n{1}"
771 raise ValueError(mes.format(whlname, "\n".join(regex_wheel_versions)))
772 if len(find) > 1:
773 mes = "Too many options for '{0}'\nOPTIONS\n{1}\nREGEX\n{2}"
774 raise ValueError(mes.format(
775 whlname, find, "\n".join(regex_wheel_versions)))
776 if isinstance(find[0], tuple):
777 return find[0][0]
778 else:
779 return find[0]