Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1""" 

2@file 

3@brief Functions to get module version, license, dependencies 

4""" 

5import sys 

6import re 

7import warnings 

8import functools 

9from .install_cmd_helper import run_cmd, get_pip_program 

10from .module_install_exceptions import MissingPackageOnPyPiException, AnnoyingPackageException 

11from .module_install_exceptions import ConfigurationError, MissingVersionOnPyPiException, WrongVersionError, MissingVersionWheelException 

12from .install_cmd_regex import regex_wheel_versions 

13 

14if sys.version_info[0] == 2: 

15 FileNotFoundError = Exception 

16 import xmlrpclib as xmlrpc_client 

17 TimeoutError = Exception 

18else: 

19 import xmlrpc.client as xmlrpc_client 

20 # from importlib import reload 

21 

22 

23annoying_modules = {"pygame", "liblinear", "mlpy", "VideoCapture", 

24 "libsvm", "opencv_python", "scikits.cuda", 

25 "NLopt"} 

26 

27 

28def call_get_installed_distributions(local_only=True, skip=None, include_editables=True, 

29 editables_only=False, user_only=False, use_cmd=False): 

30 """ 

31 Directs call to function *get_installed_distributions* from :epkg:`pip`. 

32 

33 Return a list of installed Distribution objects. 

34 

35 @param local_only if True (default), only return installations 

36 local to the current virtualenv, if in a virtualenv. 

37 @param skip argument is an iterable of lower-case project names to 

38 ignore; defaults to ``pip.compat.stdlib_pkgs`` (if *skip* is None) 

39 @param editables if False, don't report editables. 

40 @param editables_only if True , only report editables. 

41 @param user_only if True , only report installations in the user 

42 site directory. 

43 @param use_cmd if True, use a different process (updated package list) 

44 @return list of installed Distribution objects. 

45 """ 

46 if use_cmd: 

47 raise NotImplementedError("use_cmd should be False") 

48 # we disable this line, it fails on travis 

49 # reload(pip._vendor.pkg_resources) 

50 from pip._internal.utils.misc import get_installed_distributions 

51 if skip is None: 

52 try: 

53 # for pip >= 0.18.1 

54 from pip._internal.utils.compat import stdlib_pkgs 

55 except ImportError: 

56 try: 

57 # for pip>=10.0 and pip < 0.18.1 

58 from pip._internal.compat import stdlib_pkgs 

59 except ImportError: 

60 # for pip<10.0 

61 from pip.compat import stdlib_pkgs 

62 skip = stdlib_pkgs 

63 return get_installed_distributions(local_only=local_only, skip=skip, 

64 include_editables=include_editables, 

65 editables_only=editables_only, 

66 user_only=user_only) 

67 

68 

69_get_module_version_manual_memoize = {} 

70 

71 

72def get_module_version(module, use_cmd=False): 

73 """ 

74 Returns a dictionary ``{ module: version }``. 

75 

76 @param module unused, None 

77 @param use_cmd use command line 

78 @return dictionary 

79 """ 

80 if module is not None: 

81 modl = module.lower() 

82 res = get_module_version(None, use_cmd=use_cmd) 

83 return res.get(modl, None) 

84 

85 global _get_module_version_manual_memoize 

86 if len(_get_module_version_manual_memoize) > 0: 

87 return _get_module_version_manual_memoize 

88 

89 res = {} 

90 

91 if use_cmd: 

92 prog = get_pip_program() 

93 cmd = prog + " list" 

94 out, err = run_cmd(cmd, wait=True, fLOG=None) 

95 

96 if err is not None and len(err) > 0: 

97 if len(err.split("\n")) > 3 or \ 

98 "You should consider upgrading via the 'pip install --upgrade pip' command." not in err: 

99 raise Exception("unable to run, #lines {0}\nCMD:\n{3}\nERR-J:\n{1}\nOUT:\n{2}".format( 

100 len(err.split("\n")), err, out, cmd)) 

101 lines = out.split("\n") 

102 

103 for line in lines: 

104 if "(" in line: 

105 spl = line.split() 

106 if len(spl) == 2: 

107 a = spl[0] 

108 b = spl[1].strip(" \n\r") 

109 res[a] = b.strip("()") 

110 al = a.lower() 

111 if al != a: 

112 res[al] = res[a] 

113 else: 

114 # local_only must be False to get all modules 

115 # not only the ones installed in the virtual environment 

116 dist = call_get_installed_distributions(local_only=False) 

117 if len(dist) == 0: 

118 raise ConfigurationError("no installed module, unexpected (pip should be there): " + 

119 "sys.executable={0}, sys.prefix={1}, sys.base_prefix={2}".format( 

120 sys.executable, sys.prefix, sys.base_prefix)) 

121 for mod in dist: 

122 al = mod.key.lower() 

123 a = mod.key 

124 try: 

125 v = mod.version 

126 except ValueError: 

127 v = "UNKNOWN" 

128 res[a] = v 

129 if a != al: 

130 res[al] = v 

131 

132 _get_module_version_manual_memoize.update(res) 

133 return res 

134 

135 

136def is_installed(name): 

137 """ 

138 Tells if a module is installed or not. 

139 

140 @param name module name 

141 @return boolean 

142 """ 

143 return get_module_version(name) is not None 

144 

145 

146_get_module_metadata_manual_memoize = {} 

147 

148 

149def get_module_metadata(module, use_cmd=False, refresh_cache=False): 

150 """ 

151 Returns a dictionary ``{ module: metadata }``. 

152 

153 @param module unused, None 

154 @param refresh_cache refresh the cache before getting metadata 

155 @return dictionary 

156 """ 

157 if module is not None: 

158 modl = module.lower() 

159 res = get_module_metadata( 

160 None, use_cmd=use_cmd, refresh_cache=refresh_cache) 

161 return res.get(modl, None) 

162 

163 global _get_module_metadata_manual_memoize 

164 if not refresh_cache and len(_get_module_metadata_manual_memoize) > 0: 

165 return _get_module_metadata_manual_memoize 

166 

167 # local_only must be False to get all modules 

168 # not only the ones installed in the virtual environment 

169 dist = call_get_installed_distributions(local_only=False, use_cmd=use_cmd) 

170 if len(dist) == 0: 

171 raise ConfigurationError("no installed module, unexpected (pip should be there): " + 

172 "sys.executable={0}, sys.prefix={1}, sys.base_prefix={2}".format( 

173 sys.executable, sys.prefix, sys.base_prefix)) 

174 res = {} 

175 for mod in dist: 

176 d = {} 

177 lines = mod._get_metadata(mod.PKG_INFO) 

178 for line in lines: 

179 if sys.version_info[0] == 2: 

180 typstr = str # unicode# 

181 if not isinstance(line, typstr): 

182 line = typstr(line, encoding="utf8", errors="ignore") 

183 try: 

184 spl = line.split(":") 

185 except UnicodeDecodeError: 

186 warnings.warn("UnicodeDecodeError with: " + line) 

187 continue 

188 key = spl[0].strip() 

189 value = ":".join(spl[1:]).strip() 

190 if key not in d: 

191 d[key] = value 

192 else: 

193 if not isinstance(d[key], list): 

194 d[key] = [d[key]] 

195 d[key].append(value) 

196 

197 a = mod.key 

198 res[a] = d 

199 al = mod.key.lower() 

200 if a != al: 

201 res[al] = d 

202 

203 _get_module_metadata_manual_memoize.update(res) 

204 return res 

205 

206 

207def _get_pypi_version_memoize_op(f): 

208 memo = {} 

209 

210 def helper(module_name, full_list=False, url="https://pypi.python.org/pypi"): 

211 key = module_name, full_list, url 

212 if key not in memo: 

213 memo[key] = f(module_name, full_list, url) 

214 return memo[key] 

215 return helper 

216 

217 

218_get_pypi_version_memoize = {} 

219 

220 

221def get_pypi_version(module_name, full_list=False, url="https://pypi.python.org/pypi", skip_betas=True): 

222 """ 

223 Returns the version of a package on :epkg:`pypi`, 

224 we skip alpha, beta or dev version. 

225 

226 @param module_name module name 

227 @param url pypi server 

228 @param full_list results as a list or return the last stable version 

229 @param skip_betas skip the intermediate functions 

230 @return version (str or list) 

231 

232 See also `installing_python_packages_programatically.py <https://gist.github.com/rwilcox/755524>`_, 

233 `pkgtools.pypi: PyPI interface <http://pkgtools.readthedocs.org/en/latest/pypi.html>`_. 

234 

235 It the function fails, check the status of 

236 `Python Infrastructure <https://status.python.org/>`_. 

237 It can return errors:: 

238 

239 ProtocolError: ProtocolError for pypi.python.org/pypi: 503 No healthy backends 

240 """ 

241 

242 global _get_pypi_version_memoize 

243 key = module_name, full_list, url 

244 if key in _get_pypi_version_memoize: 

245 available = _get_pypi_version_memoize[key] 

246 if full_list: 

247 return available 

248 elif available is not None and len(available) > 0: 

249 return available[0] 

250 else: 

251 return None 

252 else: 

253 

254 def pypi_package_releases(module_name, b): 

255 nbtry = 0 

256 while nbtry < 2: 

257 try: 

258 available = pypi.package_releases(module_name, True) 

259 return available 

260 except TimeoutError as e: 

261 nbtry += 1 

262 warnings.warn(e) 

263 return None 

264 

265 def _inside_loop_(pypi, module_name, tried): 

266 

267 available = pypi_package_releases(module_name, True) 

268 

269 if available is None or len(available) == 0: 

270 ntry = module_name.capitalize() 

271 if ntry not in tried: 

272 tried.append(ntry) 

273 available = pypi_package_releases(tried[-1], True) 

274 

275 if available is None or len(available) == 0: 

276 ntry = module_name.replace("-", "_") 

277 if ntry not in tried: 

278 tried.append(ntry) 

279 available = pypi_package_releases(tried[-1], True) 

280 

281 if available is None or len(available) == 0: 

282 ntry = module_name.replace("_", "-") 

283 if ntry not in tried: 

284 tried.append(ntry) 

285 available = pypi_package_releases(tried[-1], True) 

286 

287 if available is None or len(available) == 0: 

288 ntry = module_name.lower() 

289 if ntry not in tried: 

290 tried.append(ntry) 

291 available = pypi_package_releases(tried[-1], True) 

292 

293 if available is None or len(available) == 0: 

294 ml = module_name.lower() 

295 if ml == "markupsafe": 

296 tried.append("MarkupSafe") 

297 available = pypi_package_releases(tried[-1], True) 

298 elif ml == "flask-sqlalchemy": 

299 tried.append("Flask-SQLAlchemy") 

300 available = pypi_package_releases(tried[-1], True) 

301 elif ml == "apscheduler": 

302 tried.append("APScheduler") 

303 available = pypi_package_releases(tried[-1], True) 

304 elif ml == "datashape": 

305 tried.append("DataShape") 

306 available = pypi_package_releases(tried[-1], True) 

307 elif ml == "pycontracts": 

308 tried.append("PyContracts") 

309 available = pypi_package_releases(tried[-1], True) 

310 elif ml == "pybrain": 

311 tried.append("PyBrain") 

312 available = pypi_package_releases(tried[-1], True) 

313 elif ml == "pyexecjs": 

314 tried.append("PyExecJS") 

315 available = pypi_package_releases(tried[-1], True) 

316 elif ml == "dataspyre": 

317 tried.append("DataSpyre") 

318 available = pypi_package_releases(tried[-1], True) 

319 elif ml == "heapdict": 

320 tried.append("HeapDict") 

321 available = pypi_package_releases(tried[-1], True) 

322 elif ml == "pyreact": 

323 tried.append("PyReact") 

324 available = pypi_package_releases(tried[-1], True) 

325 elif ml == "qtpy": 

326 tried.append("QtPy") 

327 available = pypi_package_releases(tried[-1], True) 

328 elif ml == "pythonqwt": 

329 tried.append("PythonQwt") 

330 available = pypi_package_releases(tried[-1], True) 

331 elif ml == "onedrive-sdk-python": 

332 tried.append("onedrivesdk") 

333 available = pypi_package_releases(tried[-1], True) 

334 elif ml.startswith("orange3-"): 

335 s = ml.split("-")[1] 

336 ntry = "Orange3-" + s[0].upper() + s[1:] 

337 tried.append(ntry) 

338 available = pypi_package_releases(tried[-1], True) 

339 elif module_name in annoying_modules: 

340 raise AnnoyingPackageException(module_name) 

341 

342 # this raises a warning about an opened connection 

343 # see documentation of the function 

344 # del pypi 

345 

346 return available 

347 

348 tried = [module_name] 

349 

350 if sys.version_info[:2] <= (3, 4): 

351 # the client does not an implemented of __exit__ for version <= 3.4 

352 pypi = xmlrpc_client.ServerProxy(url) 

353 available = _inside_loop_(pypi, module_name, tried) 

354 else: 

355 with xmlrpc_client.ServerProxy(url) as pypi: 

356 available = _inside_loop_(pypi, module_name, tried) 

357 

358 if available is None or len(available) == 0: 

359 raise MissingPackageOnPyPiException("tried:\n" + "\n".join(tried)) 

360 

361 def filter_betas(a): 

362 spl = a.split(".") 

363 if len(spl) in (2, 3): 

364 last = spl[-1] 

365 if not skip_betas or ("a" not in last and "b" not in last and "dev" not in last): 

366 return True 

367 else: 

368 # we don't really know here, so we assume it is not 

369 return True 

370 return False 

371 

372 if available: 

373 available2 = list(filter(filter_betas, available)) 

374 if available2: 

375 _get_pypi_version_memoize[key] = available2 

376 if full_list: 

377 return available2 

378 else: 

379 return available2[0] 

380 

381 raise MissingVersionOnPyPiException( 

382 "{0}\nversion:\n{1}".format(module_name, "\n".join(available))) 

383 

384 

385def numeric_version(vers): 

386 """ 

387 convert a string into a tuple with numbers wherever possible 

388 

389 @param vers string 

390 @return tuple 

391 """ 

392 if isinstance(vers, tuple): 

393 return vers 

394 if isinstance(vers, list): 

395 raise Exception("unexpected value:" + str(vers)) 

396 spl = vers.split(".") 

397 r = [] 

398 for _ in spl: 

399 try: 

400 i = int(_) 

401 r.append(i) 

402 except ValueError: 

403 r.append(_) 

404 return tuple(r) 

405 

406 

407def compare_version(num, vers): 

408 """ 

409 Compares two versions. 

410 

411 @param num first version 

412 @param vers second version 

413 @return -1, 0, 1 

414 """ 

415 if num is None: 

416 if vers is None: 

417 return 0 

418 else: 

419 return 1 

420 if vers is None: 

421 return -1 

422 

423 if not isinstance(vers, tuple): 

424 vers = numeric_version(vers) 

425 if not isinstance(num, tuple): 

426 num = numeric_version(num) 

427 

428 if len(num) == len(vers): 

429 for a, b in zip(num, vers): 

430 if isinstance(a, int) and isinstance(b, int): 

431 if a < b: 

432 return -1 

433 elif a > b: 

434 return 1 

435 else: 

436 a = str(a) 

437 b = str(b) 

438 if a < b: 

439 return -1 

440 elif a > b: 

441 return 1 

442 return 0 

443 else: 

444 if len(num) < len(vers): 

445 num = num + (0,) * (len(vers) - len(num)) 

446 return compare_version(num, vers) 

447 else: 

448 vers = vers + (0,) * (len(num) - len(vers)) 

449 return compare_version(num, vers) 

450 

451 

452def version_consensus(v1, v2): 

453 """ 

454 *v1* and *v2* are two versions of the same module, which one to keep? 

455 

456 @param v1 version 1 

457 @param v2 version 2 

458 @return consensus 

459 

460 * ``v1=None``, ``v2='(>=1.5)'`` --> ``v='>=1.5'`` 

461 

462 To improve: 

463 

464 * ``v1='<=1.6'``, ``v2='(>=1.5)'`` --> ``v='==1.6'`` 

465 """ 

466 reg = re.compile("([><=]*)([^><=]+)") 

467 

468 def process_version(v): 

469 if isinstance(v, str # unicode# 

470 ): 

471 v = v.strip('()') 

472 find = reg.search(v) 

473 if not find: 

474 raise WrongVersionError(v) 

475 sign = find.groups()[0] 

476 number = numeric_version(find.groups()[1]) 

477 else: 

478 try: 

479 sign, number = v 

480 except ValueError as e: 

481 raise ValueError("weird format: " + str(v) + 

482 " - " + str(type(v))) from e 

483 return sign, number 

484 

485 if v1 is None: 

486 return v2 

487 elif v2 is None: 

488 return v1 

489 else: 

490 s1, n1 = process_version(v1) 

491 s2, n2 = process_version(v2) 

492 

493 if s1 not in ('<=', '==', '<', '>', '>='): 

494 raise ValueError("wrong sign {0} for v1={1}".format(s1, v1)) 

495 if s2 not in ('<=', '==', '<', '>', '>='): 

496 raise ValueError("wrong sign {0} for v1={1}".format(s2, v2)) 

497 

498 if s1 == "==": 

499 if s2 == "==": 

500 if compare_version(n1, n2) != 0: 

501 raise WrongVersionError( 

502 "incompatible version: {0}{1} and {2}{3}".format(s1, n1, s2, n2)) 

503 else: 

504 res = s1, n1 

505 

506 elif s1 == "<=": 

507 if s2 == "<=": 

508 res = s1, min(n1, n2) 

509 elif s2 == "==": 

510 if compare_version(n1, n2) < 0: 

511 raise WrongVersionError( 

512 "incompatible version: {0}{1} and {2}{3}".format(s1, n1, s2, n2)) 

513 res = s2, n2 

514 elif s2 == '<': 

515 if compare_version(n1, n2) == -1: 

516 res = s1, n1 

517 else: 

518 res = s2, n2 

519 elif s2 in ('>', '>='): 

520 if compare_version(n1, n2) <= 0: 

521 raise WrongVersionError( 

522 "incompatible version: {0}{1} and {2}{3}".format(s1, n1, s2, n2)) 

523 res = s1, n1 

524 

525 elif s1 == "<": 

526 if s2 == "<": 

527 res = s1, min(n1, n2) 

528 elif s2 == "==": 

529 if compare_version(n1, n2) <= 0: 

530 raise WrongVersionError( 

531 "incompatible version: {0}{1} and {2}{3}".format(s1, n1, s2, n2)) 

532 res = s2, n2 

533 elif s2 == '<=': 

534 if compare_version(n1, n2) <= 0: 

535 res = s1, n1 

536 else: 

537 res = s2, n2 

538 elif s2 in ('>', '>='): 

539 if compare_version(n1, n2) <= 0: 

540 raise WrongVersionError( 

541 "incompatible version: {0}{1} and {2}{3}".format(s1, n1, s2, n2)) 

542 res = s1, n1 

543 

544 elif s1 == ">=": 

545 if s2 == ">=": 

546 res = s1, max(n1, n2) 

547 elif s2 == "==": 

548 if compare_version(n1, n2) == -1: 

549 raise WrongVersionError( 

550 "incompatible version: {0}{1} and {2}{3}".format(s1, n1, s2, n2)) 

551 res = s2, n2 

552 elif s2 == '>': 

553 if compare_version(n1, n2) <= 0: 

554 res = s2, n2 

555 else: 

556 res = s1, n1 

557 elif s2 in ('<', '<='): 

558 if compare_version(n1, n2) >= 0: 

559 raise WrongVersionError( 

560 "incompatible version: {0}{1} and {2}{3}".format(s1, n1, s2, n2)) 

561 res = s2, n2 

562 

563 elif s1 == ">": 

564 if s2 == ">": 

565 res = s1, max(n1, n2) 

566 elif s2 == "==": 

567 if compare_version(n1, n2) >= 0: 

568 raise WrongVersionError( 

569 "incompatible version: {0}{1} and {2}{3}".format(s1, n1, s2, n2)) 

570 res = s2, n2 

571 elif s2 == '>=': 

572 if compare_version(n1, n2) == -1: 

573 res = s2, n2 

574 else: 

575 res = s1, n1 

576 elif s2 in ('<', '<='): 

577 if compare_version(n1, n2) == 1: 

578 raise WrongVersionError( 

579 "incompatible version: {0}{1} and {2}{3}".format(s1, n1, s2, n2)) 

580 res = s2, n2 

581 else: 

582 res = None, None 

583 

584 if res[0] is None: 

585 raise WrongVersionError( 

586 "incompatible version and wrong format: {0}{1} and {2}{3}".format(s1, n1, s2, n2)) 

587 

588 return '{0}{1}'.format(res[0], '.'.join(str(_) for _ in res[1])) 

589 

590 

591_get_module_dependencies_deps = None 

592 

593 

594def get_module_dependencies(module, use_cmd=False, deep=False, collapse=True, use_pip=None, refresh_cache=False): 

595 """ 

596 Returns the dependencies for a module. 

597 

598 @param module unused, None 

599 @param use_cmd use command line 

600 @param deep dig into dependencies of dependencies 

601 @param collapse only one row per module 

602 @param use_pip use pip to discover dependencies or not (parse metadata) 

603 @param refresh_cache refresh the cache (see below) 

604 @return list of tuple (module, version, required by as a str) 

605 or dictionary { module: (version, required by as a list) } if *collapse* is True 

606 

607 The function which uses *use_pip=True* is not fully tested, it does not 

608 return contraints (== 2.4). The function caches the results to avoid doing it again 

609 during a second execution unless *refresh_cache* is True. 

610 This function is not tested on Python 2.7. 

611 """ 

612 if use_pip is None: 

613 use_pip = not sys.platform.startswith("win") 

614 

615 def evaluate_condition(cond, full): 

616 # example python_version=="3.3" or python_version=="2.7" and extra == 

617 # \'test\' 

618 python_version = ".".join(str(_) for _ in sys.version_info[:3]) 

619 extra = "" 

620 sys_platform = sys.platform 

621 try: 

622 return eval(cond) 

623 except Exception: 

624 if "python_version" not in cond and "extra" not in cond: 

625 # probably something like cycler (>=0.10) 

626 # we don't check that 

627 return True 

628 else: 

629 mes = "Unable to evaluate condition '{0}' from '{1}', extra='{2}', python_version='{3}', sys_platform='{4}'.".format( 

630 cond, full, extra, python_version, sys_platform) 

631 raise Exception(mes) 

632 

633 if use_pip: 

634 global _get_module_dependencies_deps 

635 if _get_module_dependencies_deps is None or refresh_cache: 

636 temp = call_get_installed_distributions( 

637 local_only=False, skip=[], use_cmd=use_cmd) 

638 _get_module_dependencies_deps = dict( 

639 (p.key, (p, p.requires())) for p in temp) 

640 if module not in _get_module_dependencies_deps: 

641 raise ValueError("module {0} was not installed".format(module)) 

642 res = [] 

643 req = _get_module_dependencies_deps[module][1] 

644 if isinstance(req, list): 

645 for r in req: 

646 res.append((r.key, None, module)) 

647 else: 

648 res.append((req.key, None, module)) 

649 else: 

650 meta = get_module_metadata( 

651 module, use_cmd, refresh_cache=refresh_cache) 

652 if meta is None: 

653 raise ImportError( 

654 "unable to get metadata for module '{0}' - refresh_cache={1}".format(module, refresh_cache)) 

655 deps = [v for k, v in meta.items() 

656 if "Requires" in k and "Requires-Python" not in k] 

657 res = [] 

658 for d in deps: 

659 if not isinstance(d, list): 

660 dl = [d] 

661 else: 

662 dl = d 

663 for v in dl: 

664 spl = v.split() 

665 if len(spl) > 1: 

666 spl = [spl[0], " ".join(spl[1:])] 

667 if len(spl) == 1: 

668 key = (v, None, module) 

669 else: 

670 conds = spl[1].split(";") 

671 ok = [evaluate_condition(cond, v) for cond in conds] 

672 if not all(ok): 

673 continue 

674 key = (spl[0].strip(";"), spl[1], module) 

675 if key not in res: 

676 res.append(key) 

677 

678 # specific filters 

679 def validate_module(name): 

680 if name == "enum34" and sys.version_info[:2] > (3, ): 

681 raise NameError( 

682 "Unexpected dependency '{0}' for module '{1}'.".format(name, module)) 

683 if name == "configparser": 

684 raise NameError( 

685 "Unexpected dependency '{0}' for module '{1}'.".format(name, module)) 

686 return True 

687 

688 res = [key for key in res if validate_module(key[0])] 

689 

690 if deep: 

691 done = {module: None} 

692 mod = 1 

693 while mod > 0: 

694 mod = 0 

695 for r in res: 

696 if r[0] not in done: 

697 if r[0].lower() < 'a' or r[0].lower() > 'z' or r[0].endswith(";"): 

698 raise NameError( 

699 "A module has an unexpected name '{0}', r={1} when looking for dependencies of '{2}'.".format(r[0], r, module)) 

700 temp = get_module_dependencies( 

701 r[0], use_cmd=use_cmd, deep=deep, collapse=False, use_pip=use_pip, refresh_cache=refresh_cache) 

702 for key in temp: 

703 if key not in res: 

704 res.append(key) 

705 mod += 1 

706 done[r[0]] = None 

707 

708 if collapse: 

709 final = {} 

710 for name, version, required in res: 

711 if name not in final: 

712 final[name] = (version, [required]) 

713 else: 

714 ex = final[name][1] 

715 if required not in ex: 

716 ex.append(required) 

717 try: 

718 v = version_consensus(final[name][0], version) 

719 except WrongVersionError as e: 

720 raise WrongVersionError("unable to reconcile versions:\n{0}\n{1}".format( 

721 ex, str((name, version, required)))) from e 

722 final[name] = (v, ex) 

723 final = {k: (v[0], list(sorted(v[1]))) for k, v in final.items()} 

724 return final 

725 else: 

726 return [(name, version.strip('()') if version is not None else version, required) 

727 for name, version, required in res] 

728 

729 

730def choose_most_recent(list_name): 

731 """ 

732 Chooses the most recent version for a list of module names. 

733 

734 @param list_name list of names 

735 @return most recent version or None if the input list is empty 

736 

737 In the following case, we would choose the first option:: 

738 

739 numpy-1.10.0+mkl-cp34-none-win_amd64.whl 

740 numpy-1.9.1.0+mkl-cp34-none-win_amd64.whl 

741 """ 

742 def find_wheel(tu): 

743 for t in tu: 

744 if ".whl" in t: 

745 return t 

746 raise ValueError("unable to find a wheel in {0}".format(tu)) 

747 if len(list_name) == 0: 

748 return None 

749 if isinstance(list_name[0], tuple): 

750 list_name = [(find_wheel(_), _) for _ in list_name] 

751 else: 

752 list_name = [(_, _) for _ in list_name] 

753 

754 versions = [re.compile(_) for _ in regex_wheel_versions] 

755 

756 def search_regex(_): 

757 resv = None 

758 for version in versions: 

759 try: 

760 resv = version.search(_[0]) 

761 except TypeError as e: 

762 raise TypeError("Unable to parse '{0}'".format(_)) from e 

763 if resv is not None: 

764 return resv 

765 raise MissingVersionWheelException( 

766 "Unable to get version number for module '{}':\nREGEX\n{}".format(_, "\n".join(regex_wheel_versions))) 

767 

768 list_name = [(search_regex(_).groups()[0], _[0], _[1]) 

769 for _ in list_name] 

770 

771 def cmp(el1, el2): 

772 return compare_version(el1[0], el2[0]) 

773 

774 list_name = list(sorted(list_name, key=functools.cmp_to_key(cmp))) 

775 return list_name[-1][-1] 

776 

777 

778def get_wheel_version(whlname): 

779 """ 

780 extract the version from a wheel file, 

781 return ``2.6.0`` for ``rpy2-2.6.0-cp34-none-win_amd64.whl`` 

782 

783 @param whlname file name 

784 @return string 

785 """ 

786 find = [] 

787 for reg in regex_wheel_versions: 

788 if len(find) == 0: 

789 exp = re.compile(reg) 

790 find = exp.findall(whlname) 

791 else: 

792 break 

793 if len(find) == 0: 

794 mes = "Unable to extract version for '{0}'\nREGEX\n{1}" 

795 raise ValueError(mes.format(whlname, "\n".join(regex_wheel_versions))) 

796 if len(find) > 1: 

797 mes = "Too many options for '{0}'\nOPTIONS\n{1}\nREGEX\n{2}" 

798 raise ValueError(mes.format( 

799 whlname, find, "\n".join(regex_wheel_versions))) 

800 if isinstance(find[0], tuple): 

801 return find[0][0] 

802 else: 

803 return find[0]