Coverage for src/pymyinstall/installhelper/module_install.py: 41%

861 statements  

« prev     ^ index     » next       coverage.py v7.1.0, created at 2023-07-19 01:47 +0200

1""" 

2@file 

3@brief Various function to install various python module from various location. 

4""" 

5from __future__ import print_function 

6import sys 

7import re 

8import os 

9import time 

10import importlib 

11import datetime 

12import warnings 

13from urllib.parse import urlsplit 

14import urllib.request as urllib_request 

15import urllib.error as urllib_error 

16import importlib.util 

17import xmlrpc.client as xmlrpc_client 

18from pip import __version__ as pip_version 

19from .install_cmd_helper import python_version, run_cmd, unzip_files, get_pip_program 

20from .install_cmd_helper import get_python_program, get_file_modification_date, get_conda_program, is_conda_distribution 

21from .module_install_exceptions import MissingPackageOnPyPiException, MissingInstalledPackageException, InstallError 

22from .module_install_exceptions import DownloadError, MissingVersionWheelException, WrongWheelException, MissingWheelException 

23from .module_install_version import get_pypi_version, get_module_version, annoying_modules, get_module_metadata 

24from .module_install_version import numeric_version, compare_version, choose_most_recent, get_wheel_version 

25from .module_install_page_wheel import get_page_wheel, read_page_wheel, save_page_wheel, enumerate_links_module, extract_all_links 

26from .missing_license import missing_module_licenses 

27from .internet_settings import default_user_agent 

28from .install_cmd_regex import regex_wheel_versions 

29 

30 

31def _filter_pip_out(out): 

32 lines = out.split('\n') 

33 res = [] 

34 for line in lines: 

35 if "WARNING:" in line: 

36 continue 

37 if "Consider adding this directory to PATH" in line: 

38 continue 

39 res.append(line) 

40 return "\n".join(res).strip(' \n\r\t') 

41 

42 

43class ModuleInstall: 

44 

45 """ 

46 defines the necessary information for a module 

47 

48 .. exref:: 

49 :title: Installation from GitHub 

50 

51 :: 

52 

53 ModuleInstall("pyquickhelper", "github", 

54 "sdpython").install(temp_folder="temp") 

55 """ 

56 

57 allowedKind = ["pip", "github", "exe", "exe2", "wheel", "wheel2"] 

58 exeLocation = "http://www.xavierdupre.fr/enseignement/setup/" 

59 # https://www.lfd.uci.edu/~gohlke/pythonlibs/ stopped 

60 exeLocationXd_Default = "http://www.xavierdupre.fr/enseignement/setup/" 

61 gitexe = r"C:\Program Files (x86)\Git" 

62 github_pattern_zip = "https://github.com/{1}/{0}/archive/{2}.zip" 

63 github_pattern_git = "https://github.com/{1}/{0}.git{2}" 

64 

65 @staticmethod 

66 def is_annoying(module_name): 

67 """ 

68 some modules are not available on pipy 

69 """ 

70 return module_name in annoying_modules 

71 

72 def __init__(self, name, kind="pip", gitrepo=None, mname=None, fLOG=print, 

73 version=None, script=None, index_url=None, deps=None, 

74 purpose=None, usage=None, web=None, source=None, custom=None, 

75 branch="master", pip_options=None, overwrite=None, post=None, 

76 skip_import=False, pipgit=False): 

77 """ 

78 @param name name 

79 @param kind kind of installation (*pip*, *github*, *wheel*) 

80 @param gitrepo github repository (example: sdpython) 

81 @param mname sometimes, the module name is different from its official name 

82 @param version to install a specific version (None for the latest) 

83 @param fLOG logging function 

84 @param script some extensions are not a module but an application (such as ``spyder``), 

85 the class will check this script is available 

86 @param deps overwrite deps parameters when installing the module 

87 @param index_url to get the package from a custom pypi server 

88 @param purpose purpose of the module 

89 @param usage main usage for the module 

90 @param web website for the module, if None, default to pipy 

91 @param source to overwrite parameter *source* of methods 

92 @see me download, @see me install or @see me update. 

93 @param custom custom instructions to install, usually 

94 ``['build', 'install']`` to run 

95 ``setup.py build`` and ``setup.py install`` 

96 @param branch only necessary for install process with github 

97 @param pip_options additional options for pip (list) 

98 @param overwrite overwrite the location of the wheel 

99 @param post instructions post installation (look for this parameter 

100 in the code to see what is supported) 

101 @param pipgit install the module with ``pip + git`` instead of 

102 getting the full archive 

103 @param skip_import added to indicate the module cannot be imported 

104 

105 .. versionchanged:: 1.1 

106 Parameters *source*, *custom*, *branch*, *pip_options*, *overwrite*, *post* were added. 

107 Parameter *skip_import* was introduced to skip the checking of the installation. 

108 Parameter *pipgit* was added. 

109 """ 

110 if kind != "pip" and version is not None: 

111 raise NotImplementedError( 

112 "version can be only specified if kind=='pip'") 

113 

114 self.name = name 

115 self.kind = kind 

116 self.gitrepo = gitrepo 

117 self.version = version 

118 self.mname = mname 

119 self.script = script 

120 self.index_url = index_url 

121 self.deps = deps 

122 self.purpose = purpose 

123 self.usage = usage 

124 self.existing_version = None 

125 self.source = source 

126 self.custom = custom 

127 self.branch = branch 

128 self.pip_options = pip_options 

129 self.overwrite = overwrite 

130 self.post_installation = post 

131 self.pipgit = pipgit 

132 

133 if self.mname == self.name: 

134 raise ValueError( 

135 "Do not specify mname if it is equal to name '{0}'.".format(self.name)) 

136 if self.pipgit and self.gitrepo is None: 

137 raise ValueError("If pipgit=True, gitrepo must be specified.") 

138 if self.pipgit and kind != 'github': 

139 raise ValueError( 

140 "If pipgit=True, kind must be 'github' not '{0}'.".format(kind)) 

141 if self.version is not None and self.gitrepo is not None: 

142 raise ValueError("version must be None if gitrepo is not.") 

143 

144 if self.mname is not None and self.mname.startswith("-"): 

145 self.mname = self.mname[1:] 

146 self.skip_import = True 

147 else: 

148 self.skip_import = skip_import 

149 self.web = web if web is not None else ( 

150 "https://pypi.python.org/pypi/" + self.name) 

151 

152 if self.kind not in ModuleInstall.allowedKind: 

153 raise RuntimeError( 

154 "unable to interpret kind {0}, it should be in {1}".format( 

155 kind, ",".join( 

156 ModuleInstall.allowedKind))) 

157 if self.kind == "github" and self.gitrepo is None: 

158 raise RuntimeError("gitrepo cannot be empty") 

159 

160 self.fLOG = fLOG 

161 

162 def copy(self, version=None): 

163 """ 

164 copy the module, if version is not None, change the version number 

165 

166 @param version version number or None for unchanged 

167 @return @see cl ModuleInstall 

168 

169 .. versionadded:: 1.0 

170 """ 

171 mod = ModuleInstall(**self.as_dict()) 

172 if version is not None: 

173 mod.version = version 

174 return mod 

175 

176 def as_dict(self, rst_link=False): 

177 """ 

178 returns the members in a dictionary 

179 

180 @param rst_link if True, add rst_link, license, classifier 

181 @return dictionary 

182 """ 

183 r = dict(name=self.name, kind=self.kind, gitrepo=self.gitrepo, version=self.version, 

184 mname=self.mname, script=self.script, deps=self.deps, index_url=self.index_url, 

185 purpose=self.purpose, usage=self.usage, web=self.web, 

186 post=None if self.post_installation is None else self.post_installation.copy(), 

187 skip_import=self.skip_import, pipgit=self.pipgit) 

188 if rst_link: 

189 r["rst_link"] = "`{0} <{1}>`_".format(self.name, self.web) 

190 r["license"] = self.get_installed_license() 

191 r["installed"] = self.get_installed_version() 

192 r["classifier"] = self.get_installed_classifier() 

193 return r 

194 

195 def __cmp__(self, o): 

196 """ 

197 to sort modules 

198 

199 @param o other module 

200 @return -1, 0, 1 

201 """ 

202 def compare(v1, v2): 

203 if v1 is None: 

204 if v2 is None: 

205 return 0 

206 else: 

207 return 1 

208 else: 

209 if v2 is None: 

210 return -1 

211 else: 

212 if v1 < v2: 

213 return -1 

214 elif v1 > v2: 

215 return 1 

216 else: 

217 return 0 

218 

219 r = compare(self.usage, o.usage) 

220 if r != 0: 

221 return r 

222 r = compare(self.name.lower(), o.name.lower()) 

223 return r 

224 

225 def __lt__(self, o): 

226 """ 

227 overload operator ``<`` 

228 

229 @param o other module 

230 @return boolean 

231 """ 

232 return self.__cmp__(o) < 0 

233 

234 @staticmethod 

235 def clear_cache(): 

236 """ 

237 clear the local cache to get wheel link 

238 """ 

239 if os.path.exists(ModuleInstall._page_cache_html): 

240 os.remove(ModuleInstall._page_cache_html) 

241 if os.path.exists(ModuleInstall._page_cache_html2): 

242 os.remove(ModuleInstall._page_cache_html2) 

243 

244 @property 

245 def Purpose(self): 

246 """ 

247 returns the comment 

248 """ 

249 return "-" if self.purpose is None else self.purpose 

250 

251 @property 

252 def Script(self): 

253 """ 

254 returns the script to run if the extension is an application and not a module 

255 """ 

256 exe = os.path.split(sys.executable)[0] 

257 if sys.platform.startswith("win"): 

258 sc = os.path.join(exe, "Scripts", self.script) 

259 else: 

260 sc = os.path.join(exe, "Scripts", os.path.splitext(self.script)[0]) 

261 return sc 

262 

263 def __str__(self): 

264 """ 

265 usual 

266 """ 

267 if self.script is None: 

268 return "{0}:{1}:import {2}:v{3}".format( 

269 self.name, self.kind, self.ImportName, self.version) 

270 else: 

271 return "{0}:{1}:{2}:v{3}".format(self.name, self.kind, self.Script, self.version) 

272 

273 def __repr__(self): 

274 """ 

275 usual 

276 """ 

277 if self.script is None: 

278 return "ModuleInstall({0}:{1}:import {2}:v{3})".format( 

279 self.name, self.kind, self.ImportName, self.version) 

280 else: 

281 return "{0}:{1}:{2}:v{3}".format(self.name, self.kind, self.Script, self.version) 

282 

283 @property 

284 def ImportName(self): 

285 """ 

286 return the import name 

287 """ 

288 if self.mname is not None: 

289 return self.mname 

290 if self.name.startswith("python-"): 

291 return self.name[len("python-"):] 

292 else: 

293 return self.name 

294 

295 def is_installed_local(self): 

296 """ 

297 checks if a module is installed 

298 """ 

299 if self.script is None: 

300 if self.skip_import: 

301 try: 

302 return self.is_installed_local_cmd() 

303 except InstallError: 

304 return False 

305 else: 

306 try: 

307 if "." in self.ImportName: 

308 raise ImportError(self.ImportName) 

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

310 r = importlib.import_module(self.ImportName) 

311 return r 

312 else: 

313 r = importlib.util.find_spec(self.ImportName) 

314 return r is not None 

315 return r is not None 

316 except ImportError: 

317 txt = "import {0} # {1}".format( 

318 self.ImportName, self.name) 

319 try: 

320 exec(txt) 

321 return True 

322 except Exception: 

323 return False 

324 else: 

325 return os.path.exists(self.Script) 

326 

327 def is_installed_local_cmd(self): 

328 """ 

329 Test the module by running a command line. 

330 Does some others verifications for a specific modules such as scipy. 

331 

332 .. versionadded:: 1.1 

333 """ 

334 exe = get_python_program() 

335 if self.skip_import: 

336 cmd = exe + \ 

337 ' -u -c "import pip # pip.main(["show", "{0}"])'.format( 

338 self.name) 

339 out, err = run_cmd(cmd, fLOG=self.fLOG) 

340 if err or out is None: 

341 raise InstallError("Cannot find {0}\nCMD:\n{1}\nOUT:\n{2}\nERR-K1:\n{3}".format( 

342 self.name, cmd, out, err)) 

343 if ("Name: " + self.name) not in out: 

344 raise InstallError("Cannot find {0}\nCMD:\n{1}\nOUT:\n{2}\nERR-K3:\n{3}".format( 

345 self.name, cmd, out, err)) 

346 return True 

347 else: 

348 cmd = exe + \ 

349 ' -u -c "import {0} # {1}"'.format(self.ImportName, self.name) 

350 out, err = run_cmd(cmd, fLOG=self.fLOG) 

351 if err: 

352 raise InstallError("cannot import module {0}\nCMD:\n{1}\nOUT:\n{2}\nERR-K2:\n{3}".format( 

353 self.ImportName, cmd, out, err)) 

354 if self.name == "scipy": 

355 cmd = exe + ' -u -c "import scipy.sparse"' 

356 out, err = run_cmd(cmd, fLOG=self.fLOG) 

357 if err: 

358 if sys.platform.startswith("win") and sys.version_info[:2] >= (3, 5) and "DLL" in err: 

359 mes = ("scipy.sparse is failing, you should check that Visual Studio 2015 is " + 

360 "installed\n{0}\nCMD:\n{1}\nOUT:\n{2}\nERR-M:\n{3}") 

361 raise InstallError(mes.format( 

362 self.ImportName, cmd, out, err)) 

363 raise InstallError("scipy.sparse is failing\n{0}\nCMD:\n{1}\nOUT:\n{2}\nERR-L:\n{3}".format( 

364 self.ImportName, cmd, out, err)) 

365 return True 

366 

367 _page_cache_html2 = os.path.join( 

368 os.path.abspath(os.path.split(__file__)[0]), "page2.html") 

369 

370 def get_exewheel_url_link2(self, file_save=None, wheel=False, source=None): 

371 """ 

372 for windows, get the url of the setup using a webpage 

373 

374 @param file_save for debug purposes 

375 @param wheel returns the wheel file or the exe file 

376 @param source source of the wheels (ex: ``2`` or ``http://...``) 

377 @return url, exe name 

378 

379 .. versionchanged:: 1.1 

380 Parameter *source* was added. 

381 """ 

382 if source is None or source == "2": 

383 source = ModuleInstall.exeLocationXd_Default 

384 source_page = source.rstrip("/") + "/index_modules_list.html" 

385 

386 if "cached_page2" not in self.__dict__: 

387 page = ModuleInstall._page_cache_html2 

388 

389 exi = os.path.exists(page) 

390 if exi: 

391 dt = get_file_modification_date(page) 

392 now = datetime.datetime.now() 

393 df = now - dt 

394 if df > datetime.timedelta(1): 

395 exi = False 

396 

397 if exi: 

398 text = read_page_wheel(page) 

399 self.cached_page2 = text 

400 else: 

401 text = get_page_wheel(source_page) 

402 save_page_wheel(page, text) 

403 self.cached_page2 = text 

404 

405 page = self.cached_page2 

406 reg = re.compile('href=\\"(.*?)\\"') 

407 alls = reg.findall(page) 

408 if len(alls) == 0: 

409 keep = [] 

410 for line in page.split("\n"): 

411 lline = line.lower() 

412 if self.name in lline or (self.mname and self.mname in lline): 

413 keep.append(line) 

414 raise RuntimeError( 

415 "module " + 

416 self.name + "\nexample:\n" + "\n".join(keep)) 

417 

418 version = python_version() 

419 plat = version[0] if version[0] == "win32" else version[1] 

420 if version[1] == '64bit' and version[0] == 'win32': 

421 plat = "amd64" 

422 cp = "-cp%d%d-" % sys.version_info[:2] 

423 py = "-py%d%d-" % sys.version_info[:2] 

424 pyn = "-py%d-" % sys.version_info[0] 

425 links = [_ for _ in alls if "/" + 

426 self.name in _ and (pyn in _ or py in _ or cp in _) and (plat in _ or "-any" in _)] 

427 if len(links) == 0 and "-" in self.name: 

428 name_ = self.name.replace("-", "_") 

429 links = [_ for _ in alls if "/" + 

430 name_ in _ and (pyn in _ or py in _ or cp in _) and (plat in _ or "-any" in _)] 

431 if len(links) == 0: 

432 # exception for guidata 

433 links = [_ for _ in alls if self.name in _ and "-py2.py3-" in _] 

434 if len(links) == 0: 

435 if file_save is not None: 

436 with open(file_save, "w", encoding="utf8") as f: 

437 f.write(page) 

438 short_list = [_ for _ in alls if self.name in _] 

439 raise MissingWheelException("unable to find a single link for " + 

440 self.name + "\n" + "\n".join(short_list)) 

441 

442 # Last filter. Removes modules with a longer name. 

443 pref1 = self.name.lower() + "-" 

444 pref2 = self.name.lower().replace("-", "_") + "-" 

445 pref3 = self.name.lower().replace("_", "-") + "-" 

446 

447 def filter_cond(name): 

448 name = name.split("/")[-1].lower() 

449 return name.startswith(pref1) or name.startswith(pref2) or name.startswith(pref3) 

450 

451 links_ = [_ for _ in links if filter_cond(_)] 

452 if len(links_) == 0: 

453 prefs = "\npref1={0}\npref2={1}\npref3={2}".format( 

454 pref1, pref2, pref3) 

455 raise MissingWheelException("unable to find a single link for " + 

456 self.name + "\n" + "\n".join(links) + prefs) 

457 links = links_ 

458 

459 links = [(lu.split("/")[-1], lu) for lu in links] 

460 links0 = links 

461 

462 if self.name == "numpy": 

463 links = [lu for lu in links if "unoptimized" not in lu[ 

464 0].lower() and "vanilla" not in lu[0].lower()] 

465 

466 if len(links) == 0: 

467 raise RuntimeError("unable to find a single link for " + 

468 self.name + 

469 "\nEX:\n" + 

470 "\n".join(str(_) for _ in links0)) 

471 

472 link = choose_most_recent(links) 

473 self.existing_version = self.extract_version(link[0]) 

474 url, whl = link[1], link[0] 

475 if not whl.endswith(".whl"): 

476 whl += ".whl" 

477 return url, whl 

478 

479 _page_cache_html = os.path.join( 

480 os.path.abspath(os.path.split(__file__)[0]), "page.html") 

481 

482 def get_exewheel_url_link(self, file_save=None, wheel=False): 

483 """ 

484 for windows, get the url of the setup using a webpage 

485 

486 @param file_save for debug purposes 

487 @param wheel returns the wheel file or the exe file 

488 @return url, exe name 

489 """ 

490 if "cached_page" not in self.__dict__: 

491 page = ModuleInstall._page_cache_html 

492 

493 exi = os.path.exists(page) 

494 if exi: 

495 dt = get_file_modification_date(page) 

496 now = datetime.datetime.now() 

497 df = now - dt 

498 if df > datetime.timedelta(1): 

499 exi = False 

500 

501 if exi: 

502 text = read_page_wheel(page) 

503 self.cached_page = text 

504 else: 

505 text = get_page_wheel(ModuleInstall.exeLocation) 

506 save_page_wheel(page, text) 

507 self.cached_page = text 

508 

509 page = self.cached_page 

510 alls = extract_all_links(page) 

511 if len(alls) == 0: 

512 keep = [] 

513 for line in page.split("\n"): 

514 lline = line.lower() 

515 if self.name in lline or (self.mname and self.mname in lline): 

516 keep.append(line) 

517 raise RuntimeError( 

518 "module " + 

519 self.name + "\nexample:\n" + "\n".join(keep)) 

520 

521 version = python_version() 

522 plat = version[0] if version[0] == "win32" else version[1] 

523 if version[1] == '64bit' and version[0] == 'win32': 

524 plat = "amd64" 

525 links = list(enumerate_links_module( 

526 self.name, alls, sys.version_info, plat)) 

527 if len(links) == 0: 

528 if file_save is not None: 

529 with open(file_save, "w", encoding="utf8") as f: 

530 f.write(page) 

531 raise MissingWheelException( 

532 "Unable to find a single link for " + self.name) 

533 nbnone = [lu for lu in links if lu[2] is None] 

534 if len(nbnone) * 2 > len(links): 

535 raise WrongWheelException("Unable to find any version in\n{0}".format( 

536 "\n".join(str(_) for _ in links))) 

537 links0 = links 

538 

539 if self.name == "numpy": 

540 links = [lu for lu in links if "unoptimized" not in lu[ 

541 0].lower() and "vanilla" not in lu[0].lower()] 

542 

543 if len(links) == 0: 

544 raise RuntimeError("unable to find a single link for " + 

545 self.name + 

546 "\nEX:\n" + 

547 "\n".join(str(_) for _ in links0)) 

548 

549 link = choose_most_recent(links) 

550 self.existing_version = self.extract_version(link[0]) 

551 if link[2] is None: 

552 raise WrongWheelException("Unable to find a proper link in {0}\n{1}".format( 

553 link, "\n".join(str(_) for _ in links))) 

554 url, whl = ModuleInstall.exeLocation + link[2], link[0] 

555 if not whl.endswith(".whl"): 

556 whl += ".whl" 

557 return url, whl 

558 

559 def unzipfiles(self, zipf, whereTo): 

560 """ 

561 unzip files from a zip archive 

562 

563 @param zipf archive 

564 @param whereTo destination folder 

565 @return list of unzipped files 

566 """ 

567 return unzip_files(zipf, whereTo, self.fLOG) 

568 

569 def extract_version(self, name): 

570 """ 

571 extract the version from a filename 

572 

573 @param name filename 

574 @return verions (str) 

575 """ 

576 res = None 

577 for i, regve in enumerate(regex_wheel_versions): 

578 reg = re.compile(regve) 

579 res = reg.search(name) 

580 if res is not None: 

581 if i == 6: 

582 return ".".join(res.groups()) 

583 else: 

584 return res.groups()[0] 

585 raise MissingVersionWheelException( 

586 "Unable to extract version number from '{0}'\nREGEX\n{1}".format(name, "\n".join(regex_wheel_versions))) 

587 

588 def download(self, temp_folder=".", force=False, unzipFile=True, 

589 file_save=None, deps=False, source=None): 

590 """ 

591 Downloads the module without installation. 

592 

593 @param temp_folder destination 

594 @param force force the installation even if already installed 

595 @param unzipFile if it can be unzipped, it will be (for github, mostly) 

596 @param file_save for debug purposes, do not change it unless you know what you are doing 

597 @param deps download the dependencies too (only available for pip) 

598 @param source overwrite source of the download, only for wheel packages, 

599 see @see me get_exewheel_url_link2 

600 @return downloaded files 

601 

602 *deps* is overwritten by *self.deps* if not None 

603 If *source* is None, it is overwritten by *self.source*. 

604 """ 

605 disable_options = {} 

606 if source is None: 

607 source = self.source 

608 kind = self.kind 

609 

610 deps = deps if self.deps is None else self.deps 

611 

612 if kind == "pip" or self.pipgit: 

613 # see https://pip.pypa.io/en/latest/reference/pip_install.html 

614 # we use pip install <package> --download=temp_folder 

615 pp = get_pip_program() 

616 if self.pipgit: 

617 br = "@" + \ 

618 self.branch if self.branch not in (None, "master") else "" 

619 link = ModuleInstall.github_pattern_git.format( 

620 self.name, self.gitrepo, br) 

621 cmd = pp + ' download git+' + link 

622 else: 

623 cmd = pp + ' download {0}'.format(self.name) 

624 if self.version is not None: 

625 cmd += "=={0}".format(self.version) 

626 if " " in temp_folder: 

627 raise FileNotFoundError( 

628 "no space allowed in folders: [" + temp_folder + "]") 

629 if deps: 

630 cmd += ' --dest={0}'.format(temp_folder) 

631 else: 

632 cmd += ' --dest={0} --no-deps'.format(temp_folder) 

633 

634 if self.index_url is not None: 

635 slash = '' if self.index_url.endswith('/') else '/' 

636 cmd += ' --no-cache-dir --index={0}{1}simple/'.format( 

637 self.index_url, slash) 

638 parsed_uri = urlsplit(self.index_url) 

639 cmd += " --trusted-host " + parsed_uri.hostname 

640 if self.pip_options is not None: 

641 diff = [_ for _ in self.pip_options if _ not in disable_options] 

642 cmd += " " + " ".join(diff) 

643 

644 out, err = run_cmd( 

645 cmd, wait=True, fLOG=self.fLOG) 

646 if "Successfully downloaded" not in out: 

647 raise DownloadError( 

648 "unable to download with pip " + 

649 str(self) + "\nCMD:\n" + 

650 cmd + "\nOUT:\n" + 

651 out + "\nERR-N:\n" + 

652 err) 

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

654 for line in lines: 

655 if line.strip().startswith("Saved "): 

656 return line.split("Saved")[-1].strip() 

657 elif line.strip().startswith("File was already downloaded"): 

658 return line.split("File was already downloaded")[-1].strip() 

659 raise FileNotFoundError( 

660 "unable to find downloaded file " + 

661 str(self) + "\nCMD:\n" + 

662 cmd + "\nOUT:\n" + 

663 out + "\nERR-O:\n" + 

664 err) 

665 

666 if kind in ("wheel", "wheel2"): 

667 if source is not None: 

668 kind = "wheel2" 

669 ver = python_version() 

670 if ver[0] != "win32": 

671 # nothing to download, you should use pip 

672 return None 

673 else: 

674 if hasattr(self, "overwrite") and self.overwrite is not None: 

675 over = self.overwrite.format(*sys.version_info[0:2]) 

676 url, whl = over, over.split("/")[-1] 

677 self.existing_version = "{0}{1}".format( 

678 *sys.version_info[0:2]) 

679 elif kind == "wheel": 

680 url, whl = self.get_exewheel_url_link( 

681 file_save=file_save, wheel=True) 

682 else: 

683 url, whl = self.get_exewheel_url_link2( 

684 file_save=file_save, wheel=True, source=source) 

685 whlname = os.path.join(temp_folder, whl) 

686 

687 exi = os.path.exists(whlname) 

688 if force or not exi: 

689 

690 self.fLOG("[pymy] downloading", whl) 

691 # self.fLOG("url", url) 

692 if self.existing_version is None: 

693 self.existing_version = self.extract_version(whl) 

694 req = urllib_request.Request( 

695 url, headers={ 

696 'User-agent': default_user_agent}) 

697 try: 

698 u = urllib_request.urlopen(req) 

699 text = u.read() 

700 u.close() 

701 except urllib_error.HTTPError as e: 

702 raise DownloadError("unable to download {} from {}".format( 

703 os.path.split(whlname)[-1], url)) from e 

704 

705 if not os.path.exists(temp_folder): 

706 os.makedirs(temp_folder) 

707 

708 if len(text) <= 1000: 

709 raise WrongWheelException("Size of downloaded wheel is too small: {0}\nurl={1}\nagent={2}".format( 

710 len(text), url, default_user_agent)) 

711 

712 self.fLOG("[pymy] writing", whl) 

713 with open(whlname, "wb") as f: 

714 f.write(text) 

715 

716 return whlname 

717 

718 elif kind == "github" and not self.pipgit: 

719 outfile = os.path.join(temp_folder, self.name + ".zip") 

720 if force or not os.path.exists(outfile): 

721 zipurl = ModuleInstall.github_pattern_zip.format( 

722 self.name, self.gitrepo, self.branch) 

723 self.fLOG("[pymy] downloading", zipurl) 

724 try: 

725 req = urllib_request.Request( 

726 zipurl, headers={ 

727 'User-agent': default_user_agent}) 

728 u = urllib_request.urlopen(req) 

729 text = u.read() 

730 u.close() 

731 except urllib_error.HTTPError as e: 

732 raise RuntimeError( 

733 "unable to get archive from: " + 

734 zipurl) from e 

735 

736 if not os.path.exists(temp_folder): 

737 os.makedirs(temp_folder) 

738 u = open(outfile, "wb") 

739 u.write(text) 

740 u.close() 

741 

742 if unzipFile: 

743 self.fLOG("[pymy] unzipping ", outfile) 

744 files = self.unzipfiles(outfile, temp_folder) 

745 return files 

746 else: 

747 return outfile 

748 

749 if kind in ("exe", "exe2"): 

750 if source is not None: 

751 kind = "exe2" 

752 ver = python_version() 

753 if ver[0] != "win32": 

754 raise RuntimeError( 

755 "this option is not available on other systems than Windows, version={0}".format(ver)) 

756 url, exe = self.get_exewheel_url_link( 

757 file_save=file_save) if kind == "exe" else self.get_exewheel_url_link2( 

758 file_save=file_save, source=source) 

759 

760 self.fLOG("[pymy] downloading", exe) 

761 req = urllib_request.Request( 

762 url, headers={ 

763 'User-agent': default_user_agent}) 

764 u = urllib_request.urlopen(req) 

765 text = u.read() 

766 u.close() 

767 

768 if not os.path.exists(temp_folder): 

769 os.makedirs(temp_folder) 

770 

771 exename = os.path.join(temp_folder, exe) 

772 self.fLOG("[pymy] writing", exe) 

773 with open(exename, "wb") as f: 

774 f.write(text) 

775 return exename 

776 

777 raise ImportError( 

778 "unknown kind: {0} for module {1}".format( 

779 kind, 

780 self.name)) 

781 

782 def get_pypi_version(self, url='https://pypi.python.org/pypi'): 

783 """ 

784 returns the version of a package on pypi 

785 

786 @param url pipy server 

787 @return version 

788 

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

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

791 """ 

792 if url == 'https://pypi.python.org/pypi': 

793 # we use a function which caches the result 

794 return get_pypi_version(self.name) 

795 else: 

796 pypi = xmlrpc_client.ServerProxy(url) 

797 available = pypi.package_releases(self.name) 

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

799 available = pypi.package_releases(self.name.capitalize()) 

800 if (available is None or len(available) == 0) and self.mname is not None: 

801 available = pypi.package_releases(self.mname) 

802 

803 if available is None: 

804 raise MissingPackageOnPyPiException( 

805 "; ".join([self.name, self.name.capitalize(), self.mname])) 

806 

807 return available[0] 

808 

809 def get_pypi_numeric_version(self): 

810 """ 

811 returns the version of a package in pypi 

812 

813 @return tuple 

814 """ 

815 vers = self.get_pypi_version() 

816 if vers is None: 

817 return None 

818 if isinstance(vers, list): 

819 v = self.get_pypi_version() 

820 raise TypeError("unexpected type: {0} -- {1}".format(vers, v)) 

821 return numeric_version(vers) 

822 

823 def get_installed_version(self): 

824 """ 

825 return the version of the installed package 

826 

827 @return version 

828 """ 

829 vers = get_module_version(None) 

830 if self.name in vers: 

831 return vers[self.name] 

832 cap = self.name.capitalize() 

833 if cap in vers: 

834 return vers[cap] 

835 cap = self.name.lower() 

836 if cap in vers: 

837 return vers[cap] 

838 cap = self.name.replace("-", "_") 

839 if cap in vers: 

840 return vers[cap] 

841 cap = self.name.replace("_", "-") 

842 if cap in vers: 

843 return vers[cap] 

844 cap = self.name.lower().replace("_", "-") 

845 if cap in vers: 

846 return vers[cap] 

847 if self.mname is not None: 

848 if self.mname in vers: 

849 return vers[self.mname] 

850 cap = self.mname.lower() 

851 if cap in vers: 

852 return vers[cap] 

853 return None 

854 

855 def get_installed_metadata(self): 

856 """ 

857 return the metadata of the installed package 

858 

859 @return dictionary 

860 """ 

861 r = get_module_metadata(self.name) 

862 if r is None: 

863 return get_module_metadata(self.mname) 

864 else: 

865 return r 

866 

867 def get_installed_license(self): 

868 """ 

869 return the license of the installed package 

870 

871 @return string 

872 """ 

873 meta = self.get_installed_metadata() 

874 if meta is None or len(meta) == 0: 

875 res = None 

876 else: 

877 res = None 

878 for k, v in meta.items(): 

879 if k.lower() == "license": 

880 res = v 

881 break 

882 adm = {None, "", "UNKNOWN"} 

883 if res is not None: 

884 if isinstance(res, list): 

885 res = [_ for _ in res if _ and _ not in adm] 

886 if len(res) > 0: 

887 res = res[0] 

888 else: 

889 res = None 

890 if res in adm: 

891 res = missing_module_licenses.get(self.name, None) 

892 return res 

893 

894 def get_installed_classifier(self): 

895 """ 

896 return the classifier of the installed package 

897 

898 @return string 

899 """ 

900 meta = self.get_installed_metadata() 

901 if meta is None: 

902 return None 

903 for k, v in meta.items(): 

904 if k.lower() == "classifier": 

905 return v 

906 return None 

907 

908 def is_installed_version(self): 

909 """ 

910 tells if a module is installed 

911 

912 @return boolean 

913 """ 

914 return self.get_installed_version() is not None 

915 

916 def get_installed_numeric_version(self): 

917 """ 

918 returns the version as number (not string) 

919 

920 @return tuple 

921 """ 

922 vers = self.get_installed_version() 

923 if vers is None: 

924 return None 

925 return numeric_version(vers) 

926 

927 def has_update(self): 

928 """ 

929 tells if the package has a newer version on pipy 

930 

931 @return boolean 

932 """ 

933 if ModuleInstall.is_annoying(self.name): 

934 return False 

935 vers = self.get_installed_numeric_version() 

936 if self.version is None: 

937 pypi = self.get_pypi_numeric_version() 

938 return compare_version(pypi, vers) > 0 

939 else: 

940 num = numeric_version(self.version) 

941 return compare_version(num, vers) > 0 

942 

943 def _check_installation(self): 

944 """ 

945 some modules uninstall and install modules with another version number, 

946 we try to track that 

947 """ 

948 try: 

949 import numpy 

950 if compare_version(numpy.__version__, "1.10") < 0: 

951 raise InstallError( 

952 "numpy does not have a goof version number, it should be >= 1.10 not {0}".format(numpy.__version__)) 

953 except ImportError: 

954 # not installed 

955 pass 

956 return True 

957 

958 def install(self, force_kind=None, force=False, temp_folder=".", 

959 log=False, options=None, deps=False, source=None, 

960 custom=None, post=None, out_streams=None): 

961 """ 

962 Installs the package. 

963 

964 @param force_kind overwrite self.kind 

965 @param force force the installation even if already installed 

966 @param temp_folder folder where to download the setup 

967 @param log display logs or not 

968 @param options other options to add to the command line (see below) in a list 

969 @param deps install the dependencies too (only available for pip) 

970 @param source overwrite the source of the wheels, 

971 see @see me get_exewheel_url_link2 

972 @param custom overwrite parameters in ``self.custom`` 

973 @param post instructions post installation (see the cnostructor for more help) 

974 @param out_streams if it is a list, the function will add standard outputs 

975 @return boolean 

976 

977 The options mentioned in parameter ``options`` 

978 are described here: `pip install <http://www.pip-installer.org/en/latest/usage.html>`_ 

979 or `setup.py options <http://docs.python.org/3.4/install/>`_ if you 

980 installing a module from github. 

981 

982 .. versionchanged:: 1.0 

983 *deps* is overwritten by *self.deps* if not None 

984 

985 .. versionchanged:: 1.1 

986 On Anaconda (based on function @see fn is_conda_distribution), we try *conda* first 

987 before switching to the regular way if it did not work. 

988 Exception were changed from ``Exception`` to ``InstallError``. 

989 Parameter *source* was added, if None, it is overwritten by *self.source*. 

990 Parameter *custom* was added, it works the same as *source*. 

991 Parameter *post* was added. 

992 Parameter *out_streas* added. 

993 """ 

994 if source is None: 

995 source = self.source 

996 if post is None: 

997 post = self.post_installation 

998 if not force and force_kind is None and is_conda_distribution(): 

999 try: 

1000 return self.install(force_kind="conda", force=True, temp_folder=temp_folder, 

1001 log=log, options=options, deps=deps) 

1002 except InstallError as e: 

1003 warnings.warn(str(e)) 

1004 # we try the regular way now 

1005 

1006 if not force and self.is_installed_version(): 

1007 return True 

1008 

1009 deps = deps if self.deps is None else self.deps 

1010 

1011 if options is None: 

1012 options = self.pip_options 

1013 if options is None: 

1014 options = [] 

1015 

1016 kind = force_kind if force_kind is not None else self.kind 

1017 add = (" with " + kind) if kind != self.kind else "" 

1018 self.fLOG("[pymy] installation of " + str(self) + add) 

1019 ret = None 

1020 custom = custom or self.custom 

1021 

1022 if kind == "pip" or self.pipgit: 

1023 if custom is not None: 

1024 raise NotImplementedError( 

1025 "custom must be None not '{0}' when kind is '{1}'".format(custom, kind)) 

1026 pp = get_pip_program() 

1027 if self.pipgit: 

1028 br = "@" + \ 

1029 self.branch if self.branch not in (None, "master") else "" 

1030 link = ModuleInstall.github_pattern_git.format( 

1031 self.name, self.gitrepo, br) 

1032 cmd = pp + ' install git+' + link 

1033 else: 

1034 cmd = pp + " install {0}".format(self.name) 

1035 if self.version is not None: 

1036 cmd += "=={0}".format(self.version) 

1037 if len(options) > 0: 

1038 cmd += " " + " ".join(options) 

1039 

1040 if not deps: 

1041 cmd += ' --no-deps' 

1042 

1043 if self.index_url is not None: 

1044 slash = '' if self.index_url.endswith('/') else '/' 

1045 cmd += ' --no-cache-dir --index={0}{1}simple/'.format( 

1046 self.index_url, slash) 

1047 else: 

1048 cmd += " --cache-dir={0}".format(temp_folder) 

1049 

1050 if self.name == "kivy-garden": 

1051 memo = sys.argv 

1052 sys.argv = [] 

1053 out, err = run_cmd( 

1054 cmd, wait=True, fLOG=self.fLOG) 

1055 out = _filter_pip_out(out) 

1056 if out_streams is not None: 

1057 out_streams.append((cmd, out, err)) 

1058 if self.name == "kivy-garden": 

1059 sys.argv = memo 

1060 

1061 success2 = "Requirement already up-to-date: " + self.name 

1062 uptodate = success2.replace("-", "_") in out.replace("-", "_") 

1063 

1064 if "No distributions matching the version" in out: 

1065 mes = "(1) unable to install with pip {0}\nCMD:\n{1}\nOUT:\n{2}\nERR-P:\n{3}".format( 

1066 str(self), cmd, out, err) 

1067 raise InstallError(mes) 

1068 if "Testing of typecheck-decorator passed without failure." in out: 

1069 ret = True 

1070 elif "Successfully installed" not in out and not uptodate: 

1071 if "error: Unable to find vcvarsall.bat" in out: 

1072 url = "http://www.xavierdupre.fr/blog/2013-07-07_nojs.html" 

1073 mes = "(2) unable to install with pip {0}\nread:\n{1}\nCMD:\n{2}\nOUT:\n{3}\nERR-P:\n{4}".format( 

1074 str(self), url, cmd, out, err) 

1075 raise InstallError(mes) 

1076 if "Requirement already satisfied" not in out and not uptodate: 

1077 mes = "(3) unable to install with pip {0}\nCMD:\n{1}\nOUT:\n{2}\nERR-Q:\n{3}".format( 

1078 str(self), cmd, out, err) 

1079 raise InstallError(mes) 

1080 else: 

1081 ret = not uptodate 

1082 

1083 elif kind == "conda": 

1084 if custom is not None: 

1085 raise NotImplementedError( 

1086 "custom must be None not '{0}' when kind is '{1}'".format(custom, kind)) 

1087 if "--upgrade" in options: 

1088 options = [_ for _ in options if _ != "--upgrade"] 

1089 command = "update" 

1090 else: 

1091 command = "install" 

1092 pp = get_conda_program() 

1093 cmd = pp + " {1} {0}".format(self.name, command) 

1094 if self.version is not None: 

1095 cmd += "=={0}".format(self.version) 

1096 if len(options) > 0: 

1097 cmd += " " + " ".join(options) 

1098 

1099 cmd += " --no-pin --yes --quiet" 

1100 if not deps: 

1101 cmd += ' --no-deps' 

1102 

1103 out, err = run_cmd( 

1104 cmd, wait=True, fLOG=self.fLOG) 

1105 if out_streams is not None: 

1106 out_streams.append((cmd, out, err)) 

1107 if "No distributions matching the version" in out or \ 

1108 "No packages found in current linux" in out: 

1109 mes = "(4) unable to install with conda {0}\nCMD:\n{1}\nOUT:\n{2}\nERR-R:\n{3}".format( 

1110 str(self), cmd, out, err) 

1111 raise InstallError(mes) 

1112 if "Testing of typecheck-decorator passed without failure." in out: 

1113 ret = True 

1114 elif "Successfully installed" not in out: 

1115 if "error: Unable to find vcvarsall.bat" in out: 

1116 url = "http://www.xavierdupre.fr/blog/2013-07-07_nojs.html" 

1117 mes = "(5) unable to install with conda {0}\nread:\n{1}\nCMD:\n{2}\nOUT:\n{3}\nERR-S:\n{4}".format( 

1118 str(self), url, cmd, out, err) 

1119 raise InstallError(mes) 

1120 if "Requirement already satisfied" not in out: 

1121 mes = "(6) unable to install with conda {0}\nCMD:\n{1}\nOUT:\n{2}\nERR-T:\n{3}".format( 

1122 str(self), cmd, out, err) 

1123 raise InstallError(mes) 

1124 else: 

1125 ret = True 

1126 

1127 elif kind in ("wheel", "wheel2"): 

1128 if custom is not None: 

1129 raise NotImplementedError( 

1130 "custom must be None not '{0}' when kind is '{1}'".format(custom, kind)) 

1131 ver = python_version() 

1132 if ver[0] != "win32": 

1133 ret = self.install("pip") 

1134 whlname = self.name 

1135 ret = True 

1136 else: 

1137 whlname = self.download( 

1138 temp_folder=temp_folder, 

1139 force=force, 

1140 unzipFile=True, 

1141 source=source) 

1142 vers = self.get_installed_numeric_version() 

1143 ret = True 

1144 if vers is not None: 

1145 whlvers = numeric_version(get_wheel_version(whlname)) 

1146 if compare_version(vers, whlvers) >= 0: 

1147 self.fLOG("[pymy] skipping, no newer version {0} <= {1}: whl= {2}".format( 

1148 whlvers, vers, whlname)) 

1149 ret = False 

1150 if ret: 

1151 self.fLOG("[pymy] installing", os.path.split(whlname)[-1]) 

1152 

1153 pip = get_pip_program() 

1154 cmd = pip + " install {0}".format(whlname) 

1155 if self.version is not None: 

1156 cmd += "=={0}".format(self.version) 

1157 if len(options) > 0: 

1158 opts = list(options) 

1159 if len(opts): 

1160 cmd += " " + " ".join(opts) 

1161 if not deps: 

1162 cmd += ' --no-deps' 

1163 

1164 out, err = run_cmd( 

1165 cmd, wait=True, fLOG=self.fLOG) 

1166 if out_streams is not None: 

1167 out_streams.append((cmd, out, err)) 

1168 if "No distributions matching the version" in out: 

1169 mes = "(7) unable to install with wheel {0}\nCMD:\n{1}\nOUT:\n{2}\nERR-U:\n{3}".format( 

1170 str(self), cmd, out, err) 

1171 raise InstallError(mes) 

1172 if "Testing of typecheck-decorator passed without failure." in out: 

1173 ret = True 

1174 elif "Successfully installed" not in out: 

1175 if "error: Unable to find vcvarsall.bat" in out: 

1176 url = "http://www.xavierdupre.fr/blog/2013-07-07_nojs.html" 

1177 mes = "(8) unable to install with wheel {0}\nread:\n{1}\nCMD:\n{2}\nOUT:\n{3}\nERR-V:\n{4}".format( 

1178 str(self), url, cmd, out, err) 

1179 raise InstallError(mes) 

1180 if "Requirement already satisfied" not in out and "Requirement already up-to-date" not in out: 

1181 mes = "(9) unable to install with wheel {0}\nCMD:\n{1}\nOUT:\n{2}\nERR-W:\n{3}".format( 

1182 str(self), cmd, out, err) 

1183 raise InstallError(mes) 

1184 else: 

1185 ret = True 

1186 

1187 elif kind == "github" and not self.pipgit: 

1188 # the following code requires admin rights 

1189 # if python_version()[0].startswith("win") and kind == "git" and not os.path.exists(ModuleInstall.gitexe) : 

1190 # raise FileNotFoundError("you need to install github first: see http://windows.github.com/") 

1191 # if python_version()[0].startswith("win"): 

1192 # os.chdir(os.path.join(ModuleInstall.gitexe,"bin")) 

1193 # cmd = pip + " install -e 

1194 # git://github.com/{0}/{1}-python.git#egg={1}".format(self.gitrepo, 

1195 # self.name) 

1196 

1197 files = self.download(temp_folder=temp_folder, 

1198 force=force, unzipFile=True) 

1199 setu = [_ for _ in files if _.endswith("setup.py")] 

1200 if len(setu) == 0: 

1201 raise RuntimeError( 

1202 "unable to find setup.py for module " + 

1203 self.name) 

1204 if len(setu) > 1: 

1205 setu = [(len(_), _) for _ in setu] 

1206 setu.sort() 

1207 if setu[0][0] == setu[1][0]: 

1208 raise InstallError( 

1209 "(10) more than one setup.py for module " + 

1210 self.name + 

1211 "\n" + 

1212 "\n".join( 

1213 str(_) for _ in setu)) 

1214 self.fLOG( 

1215 "[pymy] warning: more than one setup: " + str(setu)) 

1216 setu = [setu[0][1]] 

1217 setu = os.path.abspath(setu[0]) 

1218 

1219 self.fLOG("[pymy] install ", setu[0]) 

1220 cwd = os.getcwd() 

1221 os.chdir(os.path.split(setu)[0]) 

1222 if custom is None: 

1223 custom = ["install"] 

1224 cmds = [] 

1225 for command in custom: 

1226 cmd1 = "{0} setup.py {1}".format( 

1227 sys.executable.replace("pythonw.exe", "python.exe"), 

1228 command) 

1229 cmds.append(cmd1) 

1230 

1231 def enumerate_filtered_option(options): 

1232 for o in options: 

1233 if o not in ('--no-deps', '--upgrade'): 

1234 yield o 

1235 

1236 filter_options = list(enumerate_filtered_option(options)) 

1237 if len(filter_options) > 0: 

1238 cmds[-1] += " " + " ".join(filter_options) 

1239 

1240 if deps: 

1241 # it will not work 

1242 # cmd += ' --no-deps' 

1243 pass 

1244 

1245 outs = "" 

1246 errs = "" 

1247 for cmd in cmds: 

1248 out, err = run_cmd( 

1249 cmd, wait=True, fLOG=self.fLOG) 

1250 if out_streams is not None: 

1251 out_streams.append((cmd, out, err)) 

1252 if len(outs) > 0: 

1253 outs += "\n" 

1254 if len(errs) > 0: 

1255 errs += "\n" 

1256 outs += out 

1257 errs += err 

1258 os.chdir(cwd) 

1259 

1260 out, err = outs, errs 

1261 if "Successfully installed" not in out and "install C" not in out: 

1262 if "Finished processing dependencies" not in out: 

1263 raise InstallError( 

1264 "(11) unable to install with github " + 

1265 str(self) + 

1266 "\nOUT:\n" + 

1267 out + 

1268 "\nERR-X:\n" + 

1269 err) 

1270 self.fLOG( 

1271 "warning: ``Successfully installed`` or ``install C`` not found") 

1272 if "Permission denied" in out: 

1273 raise PermissionError(" ".join(["(12) unable to install with github", str(self), "\n--CMD--:\n", 

1274 "\n".join(cmds), "\n--OUT--\n", out, "\n--ERR-Y--\n", err])) 

1275 ret = True 

1276 

1277 elif kind == "exe": 

1278 if custom is not None: 

1279 raise NotImplementedError( 

1280 "custom must be None not '{0}' when kind is '{1}'".format(custom, kind)) 

1281 ver = python_version() 

1282 if ver[0] != "win32": 

1283 ret = self.install("pip") 

1284 else: 

1285 exename = self.download( 

1286 temp_folder=temp_folder, 

1287 force=force, 

1288 unzipFile=True, 

1289 source=source) 

1290 self.fLOG("[pymy] executing", os.path.split(exename)[-1]) 

1291 out, err = run_cmd( 

1292 exename + " /s /qn /SILENT", wait=True, fLOG=self.fLOG) 

1293 if out_streams is not None: 

1294 out_streams.append((cmd, out, err)) 

1295 ret = len(err) == 0 

1296 

1297 elif kind == "exe2": 

1298 if custom is not None: 

1299 raise NotImplementedError( 

1300 "custom must be None not '{0}' when kind is '{1}'".format(custom, kind)) 

1301 ver = python_version() 

1302 if ver[0] != "win32": 

1303 ret = self.install("pip") 

1304 else: 

1305 exename = self.download( 

1306 temp_folder=temp_folder, 

1307 force=force, 

1308 unzipFile=True, 

1309 source=source) 

1310 self.fLOG("[pymy] executing", os.path.split(exename)[-1]) 

1311 out, err = run_cmd( 

1312 exename + " /s /qn", wait=True, fLOG=self.fLOG) 

1313 if out_streams is not None: 

1314 out_streams.append((cmd, out, err)) 

1315 ret = len(err) == 0 

1316 else: 

1317 raise ImportError( 

1318 "unknown kind: {0} for module {1}".format( 

1319 kind, 

1320 self.name)) 

1321 

1322 if ret is not None and ret: 

1323 self._check_installation() 

1324 

1325 # at this stage, there is a bug, for some executable, the execution 

1326 # takes longer than expected 

1327 # if not self.is_installed_version() : 

1328 # raise RuntimeError("unable to install module: {0}, str:{1}".format(self.name, self)) 

1329 

1330 if ret is not None and ret and self.script is not None: 

1331 if sys.platform.startswith("win"): 

1332 # here, we have to wait until the script is installed 

1333 ti = 0 

1334 while not self.is_installed_local(): 

1335 time.sleep(0.5) 

1336 ti += 0.5 

1337 if ti > 60: 

1338 self.fLOG( 

1339 "wait for the end of execution of ", 

1340 self.name) 

1341 ti = 0 

1342 

1343 # we add set path=%path%;PYTHONPATH 

1344 with open(self.Script, "r") as f: 

1345 full = f.read() 

1346 

1347 exe = os.path.split(sys.executable)[0] 

1348 cmd = "set path=%path%;{0}".format(exe) 

1349 if cmd not in full: 

1350 self.fLOG("[pymy] add {0} to {1}".format(cmd, self.Script)) 

1351 with open(self.Script, "w") as f: 

1352 if full.startswith("@echo off"): 

1353 f.write( 

1354 full.replace( 

1355 "@echo off", 

1356 "@echo off\n{0}".format(cmd))) 

1357 else: 

1358 f.write(cmd) 

1359 f.write("\n") 

1360 f.write(full) 

1361 

1362 if ret is not None and post is not None: 

1363 self.fLOG("[pymy] _ run_post_installation [begin]", post) 

1364 ret = self.run_post_installation(post) 

1365 self.fLOG("[pymy] _ run_post_installation [end]") 

1366 

1367 if ret is not None and ret: 

1368 # we check the module was properly installed 

1369 if not self.is_installed_local_cmd(): 

1370 raise InstallError( 

1371 "** unable to install module {0}, unable to import it".format(self.name)) 

1372 

1373 return ret 

1374 

1375 def run_post_installation(self, post): 

1376 """ 

1377 Run instructions post installation 

1378 

1379 @param post dictionary, instructions post installation 

1380 @return boolean 

1381 

1382 Example: 

1383 

1384 :: 

1385 

1386 post = dict( 

1387 cmd_python="Scripts\\\\pywin32_postinstall.py -install") 

1388 

1389 .. versionadded:: 1.1 

1390 """ 

1391 if not isinstance(post, dict): 

1392 raise TypeError("expecting a dictionary") 

1393 for k, v in post.items(): 

1394 if k == "cmd_python": 

1395 # processed just above 

1396 continue 

1397 if k == "pre_cmd": 

1398 if v == "module_install_preprocess": 

1399 self.fLOG("[pymy] _ module_install_preprocess [begin]") 

1400 self.module_install_preprocess(post) 

1401 self.fLOG("[pymy] _ module_install_preprocess [end]") 

1402 else: 

1403 raise ValueError( 

1404 "Unable to interpret value '{0}'.".format(v)) 

1405 else: 

1406 raise KeyError("Unable to interpret command '{0}'".format(k)) 

1407 for k, v in post.items(): 

1408 if k == "cmd_python": 

1409 exe = os.path.abspath(sys.executable) 

1410 cmd = '"{0}" {1}'.format(exe, v.format(os.path.dirname(exe))) 

1411 out, err = run_cmd(cmd, wait=True, fLOG=self.fLOG) 

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

1413 raise InstallError( 

1414 "Post installation script failed.\nCMD\n{0}\nOUT\n{1}\nERR-Z\n{2}".format(cmd, out, err)) 

1415 self.fLOG("[pymy] OUT:\n{0}".format(out)) 

1416 elif k == "pre_cmd": 

1417 # processed just above 

1418 continue 

1419 else: 

1420 raise KeyError("Unable to interpret command '{0}'".format(k)) 

1421 return True 

1422 

1423 def update(self, force_kind=None, force=False, temp_folder=".", 

1424 log=False, options=None, deps=False, source=None): 

1425 """ 

1426 Updates the package if necessary, we use 

1427 ``pip install <module_name> --upgrade --no-deps``, 

1428 

1429 @param force_kind overwrite self.kind 

1430 @param force force the installation even if not need to update 

1431 @param temp_folder folder where to download the setup 

1432 @param log display logs or not 

1433 @param options others options to add to the command line (see below) an a list 

1434 @param deps download the dependencies too (only available for pip) 

1435 @param source overwrite the source of the wheel, see @see me get_exewheel_url_link2 

1436 @return boolean 

1437 

1438 The options mentioned in parameter ``options`` 

1439 are described here: `pip install <http://www.pip-installer.org/en/latest/usage.html>`_ 

1440 or `setup.py options <http://docs.python.org/3.4/install/>`_ if you 

1441 installing a module from github. 

1442 

1443 .. versionchanged:: 1.1 

1444 Parameter *source* was added, if None, it is overwritten by *self.source*. 

1445 """ 

1446 if source is None: 

1447 source = self.source 

1448 if ModuleInstall.is_annoying(self.name): 

1449 return False 

1450 

1451 if not self.is_installed_version(): 

1452 raise MissingInstalledPackageException(self.name) 

1453 

1454 if not force and not self.has_update(): 

1455 return True 

1456 

1457 self.fLOG("[pymy] update of ", self) 

1458 

1459 options = [] if options is None else list(options) 

1460 for opt in ["--upgrade", "--no-deps"]: 

1461 if opt not in options: 

1462 if not deps or opt == "--no-deps": 

1463 options.append(opt) 

1464 

1465 res = self.install(force_kind=force_kind, force=True, 

1466 temp_folder=temp_folder, log=log, options=options, source=source) 

1467 return res 

1468 

1469 def module_install_preprocess(self, post): 

1470 """ 

1471 Run some preprocessing for specific modules 

1472 

1473 @param post dictionary 

1474 """ 

1475 if self.name == "pywin32": 

1476 self.fLOG("[pymy] _ module_install_preprocess", self.name) 

1477 if "cmd_python" not in post: 

1478 raise KeyError("Key 'cmd_python' is not in post.") 

1479 cmd_python = post["cmd_python"] 

1480 name = cmd_python.split()[0].format( 

1481 os.path.dirname(sys.executable)) 

1482 self.fLOG("[pymy] _ opening", name) 

1483 if not os.path.exists(name): 

1484 raise FileNotFoundError(name) 

1485 with open(name, "r") as f: 

1486 content = f.read() 

1487 repby = "os.path.exists(dest)" 

1488 if repby not in content: 

1489 self.fLOG("[pymy] _ Preprocess '{0}'".format(name)) 

1490 rep = "def CopyTo(desc, src, dest):" 

1491 content = content.replace( 

1492 rep, "{0}\n if {1}: return".format(rep, repby)) 

1493 with open(name, "w") as f: 

1494 f.write(content) 

1495 self.fLOG("[pymy] _ Done.") 

1496 else: 

1497 self.fLOG( 

1498 "Skip preprocess '{0}' because '{1}' was found.".format(name, repby)) 

1499 else: 

1500 raise ValueError( 

1501 "No preprocessing for module '{0}'.".format(self.name))