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
« 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
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')
43class ModuleInstall:
45 """
46 defines the necessary information for a module
48 .. exref::
49 :title: Installation from GitHub
51 ::
53 ModuleInstall("pyquickhelper", "github",
54 "sdpython").install(temp_folder="temp")
55 """
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}"
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
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
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'")
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
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.")
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)
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")
160 self.fLOG = fLOG
162 def copy(self, version=None):
163 """
164 copy the module, if version is not None, change the version number
166 @param version version number or None for unchanged
167 @return @see cl ModuleInstall
169 .. versionadded:: 1.0
170 """
171 mod = ModuleInstall(**self.as_dict())
172 if version is not None:
173 mod.version = version
174 return mod
176 def as_dict(self, rst_link=False):
177 """
178 returns the members in a dictionary
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
195 def __cmp__(self, o):
196 """
197 to sort modules
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
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
225 def __lt__(self, o):
226 """
227 overload operator ``<``
229 @param o other module
230 @return boolean
231 """
232 return self.__cmp__(o) < 0
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)
244 @property
245 def Purpose(self):
246 """
247 returns the comment
248 """
249 return "-" if self.purpose is None else self.purpose
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
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)
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)
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
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)
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.
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
367 _page_cache_html2 = os.path.join(
368 os.path.abspath(os.path.split(__file__)[0]), "page2.html")
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
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
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"
386 if "cached_page2" not in self.__dict__:
387 page = ModuleInstall._page_cache_html2
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
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
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))
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))
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("_", "-") + "-"
447 def filter_cond(name):
448 name = name.split("/")[-1].lower()
449 return name.startswith(pref1) or name.startswith(pref2) or name.startswith(pref3)
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_
459 links = [(lu.split("/")[-1], lu) for lu in links]
460 links0 = links
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()]
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))
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
479 _page_cache_html = os.path.join(
480 os.path.abspath(os.path.split(__file__)[0]), "page.html")
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
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
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
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
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))
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
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()]
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))
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
559 def unzipfiles(self, zipf, whereTo):
560 """
561 unzip files from a zip archive
563 @param zipf archive
564 @param whereTo destination folder
565 @return list of unzipped files
566 """
567 return unzip_files(zipf, whereTo, self.fLOG)
569 def extract_version(self, name):
570 """
571 extract the version from a filename
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)))
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.
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
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
610 deps = deps if self.deps is None else self.deps
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)
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)
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)
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)
687 exi = os.path.exists(whlname)
688 if force or not exi:
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
705 if not os.path.exists(temp_folder):
706 os.makedirs(temp_folder)
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))
712 self.fLOG("[pymy] writing", whl)
713 with open(whlname, "wb") as f:
714 f.write(text)
716 return whlname
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
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()
742 if unzipFile:
743 self.fLOG("[pymy] unzipping ", outfile)
744 files = self.unzipfiles(outfile, temp_folder)
745 return files
746 else:
747 return outfile
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)
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()
768 if not os.path.exists(temp_folder):
769 os.makedirs(temp_folder)
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
777 raise ImportError(
778 "unknown kind: {0} for module {1}".format(
779 kind,
780 self.name))
782 def get_pypi_version(self, url='https://pypi.python.org/pypi'):
783 """
784 returns the version of a package on pypi
786 @param url pipy server
787 @return version
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)
803 if available is None:
804 raise MissingPackageOnPyPiException(
805 "; ".join([self.name, self.name.capitalize(), self.mname]))
807 return available[0]
809 def get_pypi_numeric_version(self):
810 """
811 returns the version of a package in pypi
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)
823 def get_installed_version(self):
824 """
825 return the version of the installed package
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
855 def get_installed_metadata(self):
856 """
857 return the metadata of the installed package
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
867 def get_installed_license(self):
868 """
869 return the license of the installed package
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
894 def get_installed_classifier(self):
895 """
896 return the classifier of the installed package
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
908 def is_installed_version(self):
909 """
910 tells if a module is installed
912 @return boolean
913 """
914 return self.get_installed_version() is not None
916 def get_installed_numeric_version(self):
917 """
918 returns the version as number (not string)
920 @return tuple
921 """
922 vers = self.get_installed_version()
923 if vers is None:
924 return None
925 return numeric_version(vers)
927 def has_update(self):
928 """
929 tells if the package has a newer version on pipy
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
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
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.
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
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.
982 .. versionchanged:: 1.0
983 *deps* is overwritten by *self.deps* if not None
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
1006 if not force and self.is_installed_version():
1007 return True
1009 deps = deps if self.deps is None else self.deps
1011 if options is None:
1012 options = self.pip_options
1013 if options is None:
1014 options = []
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
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)
1040 if not deps:
1041 cmd += ' --no-deps'
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)
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
1061 success2 = "Requirement already up-to-date: " + self.name
1062 uptodate = success2.replace("-", "_") in out.replace("-", "_")
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
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)
1099 cmd += " --no-pin --yes --quiet"
1100 if not deps:
1101 cmd += ' --no-deps'
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
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])
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'
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
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)
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])
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)
1231 def enumerate_filtered_option(options):
1232 for o in options:
1233 if o not in ('--no-deps', '--upgrade'):
1234 yield o
1236 filter_options = list(enumerate_filtered_option(options))
1237 if len(filter_options) > 0:
1238 cmds[-1] += " " + " ".join(filter_options)
1240 if deps:
1241 # it will not work
1242 # cmd += ' --no-deps'
1243 pass
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)
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
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
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))
1322 if ret is not None and ret:
1323 self._check_installation()
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))
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
1343 # we add set path=%path%;PYTHONPATH
1344 with open(self.Script, "r") as f:
1345 full = f.read()
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)
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]")
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))
1373 return ret
1375 def run_post_installation(self, post):
1376 """
1377 Run instructions post installation
1379 @param post dictionary, instructions post installation
1380 @return boolean
1382 Example:
1384 ::
1386 post = dict(
1387 cmd_python="Scripts\\\\pywin32_postinstall.py -install")
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
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``,
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
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.
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
1451 if not self.is_installed_version():
1452 raise MissingInstalledPackageException(self.name)
1454 if not force and not self.has_update():
1455 return True
1457 self.fLOG("[pymy] update of ", self)
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)
1465 res = self.install(force_kind=force_kind, force=True,
1466 temp_folder=temp_folder, log=log, options=options, source=source)
1467 return res
1469 def module_install_preprocess(self, post):
1470 """
1471 Run some preprocessing for specific modules
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))