Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1"""
2@file
3@brief 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 pip import __version__ as pip_version
14from .install_cmd_helper import python_version, run_cmd, unzip_files, get_pip_program
15from .install_cmd_helper import get_python_program, get_file_modification_date, get_conda_program, is_conda_distribution
16from .module_install_exceptions import MissingPackageOnPyPiException, MissingInstalledPackageException, InstallError
17from .module_install_exceptions import DownloadError, MissingVersionWheelException, WrongWheelException, MissingWheelException
18from .module_install_version import get_pypi_version, get_module_version, annoying_modules, get_module_metadata
19from .module_install_version import numeric_version, compare_version, choose_most_recent, get_wheel_version
20from .module_install_page_wheel import get_page_wheel, read_page_wheel, save_page_wheel, enumerate_links_module, extract_all_links
21from .missing_license import missing_module_licenses
22from .internet_settings import default_user_agent
23from .install_cmd_regex import regex_wheel_versions
25if sys.version_info[0] == 2:
26 from urlparse import urlsplit
27 import urllib2 as urllib_request
28 import urllib2 as urllib_error
29 import xmlrpclib as xmlrpc_client
30 from codecs import open
31 FileNotFoundError = Exception
32 PermissionError = Exception
33else:
34 from urllib.parse import urlsplit
35 import urllib.request as urllib_request
36 import urllib.error as urllib_error
37 import importlib.util
38 import xmlrpc.client as xmlrpc_client
41def _filter_pip_out(out):
42 lines = out.split('\n')
43 res = []
44 for line in lines:
45 if "WARNING:" in line:
46 continue
47 if "Consider adding this directory to PATH" in line:
48 continue
49 res.append(line)
50 return "\n".join(res).strip(' \n\r\t')
53class ModuleInstall:
55 """
56 defines the necessary information for a module
58 .. exref::
59 :title: Installation from GitHub
61 ::
63 ModuleInstall("pyquickhelper", "github",
64 "sdpython").install(temp_folder="temp")
65 """
67 allowedKind = ["pip", "github", "exe", "exe2", "wheel", "wheel2"]
68 exeLocation = "https://www.lfd.uci.edu/~gohlke/pythonlibs/"
69 exeLocationXd_Default = "http://www.xavierdupre.fr/enseignement/setup/"
70 gitexe = r"C:\Program Files (x86)\Git"
71 github_pattern_zip = "https://github.com/{1}/{0}/archive/{2}.zip"
72 github_pattern_git = "https://github.com/{1}/{0}.git{2}"
74 @staticmethod
75 def is_annoying(module_name):
76 """
77 some modules are not available on pipy
78 """
79 return module_name in annoying_modules
81 def __init__(self, name, kind="pip", gitrepo=None, mname=None, fLOG=print,
82 version=None, script=None, index_url=None, deps=None,
83 purpose=None, usage=None, web=None, source=None, custom=None,
84 branch="master", pip_options=None, overwrite=None, post=None,
85 skip_import=False, pipgit=False):
86 """
87 @param name name
88 @param kind kind of installation (*pip*, *github*, *wheel*)
89 @param gitrepo github repository (example: sdpython)
90 @param mname sometimes, the module name is different from its official name
91 @param version to install a specific version (None for the latest)
92 @param fLOG logging function
93 @param script some extensions are not a module but an application (such as ``spyder``),
94 the class will check this script is available
95 @param deps overwrite deps parameters when installing the module
96 @param index_url to get the package from a custom pypi server
97 @param purpose purpose of the module
98 @param usage main usage for the module
99 @param web website for the module, if None, default to pipy
100 @param source to overwrite parameter *source* of methods
101 @see me download, @see me install or @see me update.
102 @param custom custom instructions to install, usually
103 ``['build', 'install']`` to run
104 ``setup.py build`` and ``setup.py install``
105 @param branch only necessary for install process with github
106 @param pip_options additional options for pip (list)
107 @param overwrite overwrite the location of the wheel
108 @param post instructions post installation (look for this parameter
109 in the code to see what is supported)
110 @param pipgit install the module with ``pip + git`` instead of
111 getting the full archive
112 @param skip_import added to indicate the module cannot be imported
114 .. versionchanged:: 1.1
115 Parameters *source*, *custom*, *branch*, *pip_options*, *overwrite*, *post* were added.
116 Parameter *skip_import* was introduced to skip the checking of the installation.
117 Parameter *pipgit* was added.
118 """
119 if kind != "pip" and version is not None:
120 raise NotImplementedError(
121 "version can be only specified if kind=='pip'")
123 self.name = name
124 self.kind = kind
125 self.gitrepo = gitrepo
126 self.version = version
127 self.mname = mname
128 self.script = script
129 self.index_url = index_url
130 self.deps = deps
131 self.purpose = purpose
132 self.usage = usage
133 self.existing_version = None
134 self.source = source
135 self.custom = custom
136 self.branch = branch
137 self.pip_options = pip_options
138 self.overwrite = overwrite
139 self.post_installation = post
140 self.pipgit = pipgit
142 if self.mname == self.name:
143 raise ValueError(
144 "Do not specify mname if it is equal to name '{0}'.".format(self.name))
145 if self.pipgit and self.gitrepo is None:
146 raise ValueError("If pipgit=True, gitrepo must be specified.")
147 if self.pipgit and kind != 'github':
148 raise ValueError(
149 "If pipgit=True, kind must be 'github' not '{0}'.".format(kind))
150 if self.version is not None and self.gitrepo is not None:
151 raise ValueError("version must be None if gitrepo is not.")
153 if self.mname is not None and self.mname.startswith("-"):
154 self.mname = self.mname[1:]
155 self.skip_import = True
156 else:
157 self.skip_import = skip_import
158 self.web = web if web is not None else (
159 "https://pypi.python.org/pypi/" + self.name)
161 if self.kind not in ModuleInstall.allowedKind:
162 raise Exception(
163 "unable to interpret kind {0}, it should be in {1}".format(
164 kind, ",".join(
165 ModuleInstall.allowedKind)))
166 if self.kind == "github" and self.gitrepo is None:
167 raise Exception("gitrepo cannot be empty")
169 self.fLOG = fLOG
171 def copy(self, version=None):
172 """
173 copy the module, if version is not None, change the version number
175 @param version version number or None for unchanged
176 @return @see cl ModuleInstall
178 .. versionadded:: 1.0
179 """
180 mod = ModuleInstall(**self.as_dict())
181 if version is not None:
182 mod.version = version
183 return mod
185 def as_dict(self, rst_link=False):
186 """
187 returns the members in a dictionary
189 @param rst_link if True, add rst_link, license, classifier
190 @return dictionary
191 """
192 r = dict(name=self.name, kind=self.kind, gitrepo=self.gitrepo, version=self.version,
193 mname=self.mname, script=self.script, deps=self.deps, index_url=self.index_url,
194 purpose=self.purpose, usage=self.usage, web=self.web,
195 post=None if self.post_installation is None else self.post_installation.copy(),
196 skip_import=self.skip_import, pipgit=self.pipgit)
197 if rst_link:
198 r["rst_link"] = "`{0} <{1}>`_".format(self.name, self.web)
199 r["license"] = self.get_installed_license()
200 r["installed"] = self.get_installed_version()
201 r["classifier"] = self.get_installed_classifier()
202 return r
204 def __cmp__(self, o):
205 """
206 to sort modules
208 @param o other module
209 @return -1, 0, 1
210 """
211 def compare(v1, v2):
212 if v1 is None:
213 if v2 is None:
214 return 0
215 else:
216 return 1
217 else:
218 if v2 is None:
219 return -1
220 else:
221 if v1 < v2:
222 return -1
223 elif v1 > v2:
224 return 1
225 else:
226 return 0
228 r = compare(self.usage, o.usage)
229 if r != 0:
230 return r
231 r = compare(self.name.lower(), o.name.lower())
232 return r
234 def __lt__(self, o):
235 """
236 overload operator ``<``
238 @param o other module
239 @return boolean
240 """
241 return self.__cmp__(o) < 0
243 @staticmethod
244 def clear_cache():
245 """
246 clear the local cache to get wheel link
247 """
248 if os.path.exists(ModuleInstall._page_cache_html):
249 os.remove(ModuleInstall._page_cache_html)
250 if os.path.exists(ModuleInstall._page_cache_html2):
251 os.remove(ModuleInstall._page_cache_html2)
253 @property
254 def Purpose(self):
255 """
256 returns the comment
257 """
258 return "-" if self.purpose is None else self.purpose
260 @property
261 def Script(self):
262 """
263 returns the script to run if the extension is an application and not a module
264 """
265 exe = os.path.split(sys.executable)[0]
266 if sys.platform.startswith("win"):
267 sc = os.path.join(exe, "Scripts", self.script)
268 else:
269 sc = os.path.join(exe, "Scripts", os.path.splitext(self.script)[0])
270 return sc
272 def __str__(self):
273 """
274 usual
275 """
276 if self.script is None:
277 return "{0}:{1}:import {2}:v{3}".format(
278 self.name, self.kind, self.ImportName, self.version)
279 else:
280 return "{0}:{1}:{2}:v{3}".format(self.name, self.kind, self.Script, self.version)
282 @property
283 def ImportName(self):
284 """
285 return the import name
286 """
287 if self.mname is not None:
288 return self.mname
289 if self.name.startswith("python-"):
290 return self.name[len("python-"):]
291 else:
292 return self.name
294 def is_installed_local(self):
295 """
296 checks if a module is installed
297 """
298 if self.script is None:
299 if self.skip_import:
300 try:
301 return self.is_installed_local_cmd()
302 except InstallError:
303 return False
304 else:
305 try:
306 if "." in self.ImportName:
307 raise ImportError(self.ImportName)
308 if sys.version_info[0] == 2:
309 r = importlib.import_module(self.ImportName)
310 return r
311 else:
312 r = importlib.util.find_spec(self.ImportName)
313 return r is not None
314 return r is not None
315 except ImportError:
316 txt = "import {0} # {1}".format(
317 self.ImportName, self.name)
318 try:
319 exec(txt)
320 return True
321 except Exception:
322 return False
323 else:
324 return os.path.exists(self.Script)
326 def is_installed_local_cmd(self):
327 """
328 Test the module by running a command line.
329 Does some others verifications for a specific modules such as scipy.
331 .. versionadded:: 1.1
332 """
333 exe = get_python_program()
334 if self.skip_import:
335 cmd = exe + \
336 ' -u -c "import pip # pip.main(["show", "{0}"])'.format(
337 self.name)
338 out, err = run_cmd(cmd, fLOG=self.fLOG)
339 if err or out is None:
340 raise InstallError("Cannot find {0}\nCMD:\n{1}\nOUT:\n{2}\nERR-K1:\n{3}".format(
341 self.name, cmd, out, err))
342 if ("Name: " + self.name) not in out:
343 raise InstallError("Cannot find {0}\nCMD:\n{1}\nOUT:\n{2}\nERR-K3:\n{3}".format(
344 self.name, cmd, out, err))
345 return True
346 else:
347 cmd = exe + \
348 ' -u -c "import {0} # {1}"'.format(self.ImportName, self.name)
349 out, err = run_cmd(cmd, fLOG=self.fLOG)
350 if err:
351 raise InstallError("cannot import module {0}\nCMD:\n{1}\nOUT:\n{2}\nERR-K2:\n{3}".format(
352 self.ImportName, cmd, out, err))
353 if self.name == "scipy":
354 cmd = exe + ' -u -c "import scipy.sparse"'
355 out, err = run_cmd(cmd, fLOG=self.fLOG)
356 if err:
357 if sys.platform.startswith("win") and sys.version_info[:2] >= (3, 5) and "DLL" in err:
358 mes = ("scipy.sparse is failing, you should check that Visual Studio 2015 is " +
359 "installed\n{0}\nCMD:\n{1}\nOUT:\n{2}\nERR-M:\n{3}")
360 raise InstallError(mes.format(
361 self.ImportName, cmd, out, err))
362 raise InstallError("scipy.sparse is failing\n{0}\nCMD:\n{1}\nOUT:\n{2}\nERR-L:\n{3}".format(
363 self.ImportName, cmd, out, err))
364 return True
366 _page_cache_html2 = os.path.join(
367 os.path.abspath(os.path.split(__file__)[0]), "page2.html")
369 def get_exewheel_url_link2(self, file_save=None, wheel=False, source=None):
370 """
371 for windows, get the url of the setup using a webpage
373 @param file_save for debug purposes
374 @param wheel returns the wheel file or the exe file
375 @param source source of the wheels (ex: ``2`` or ``http://...``)
376 @return url, exe name
378 .. versionchanged:: 1.1
379 Parameter *source* was added.
380 """
381 if source is None or source == "2":
382 source = ModuleInstall.exeLocationXd_Default
383 source_page = source.rstrip("/") + "/index_modules_list.html"
385 if "cached_page2" not in self.__dict__:
386 page = ModuleInstall._page_cache_html2
388 exi = os.path.exists(page)
389 if exi:
390 dt = get_file_modification_date(page)
391 now = datetime.datetime.now()
392 df = now - dt
393 if df > datetime.timedelta(1):
394 exi = False
396 if exi:
397 text = read_page_wheel(page)
398 self.cached_page2 = text
399 else:
400 text = get_page_wheel(source_page)
401 save_page_wheel(page, text)
402 self.cached_page2 = text
404 page = self.cached_page2
405 reg = re.compile('href=\\"(.*?)\\"')
406 alls = reg.findall(page)
407 if len(alls) == 0:
408 keep = []
409 for line in page.split("\n"):
410 lline = line.lower()
411 if self.name in lline or (self.mname and self.mname in lline):
412 keep.append(line)
413 raise Exception(
414 "module " +
415 self.name + "\nexample:\n" + "\n".join(keep))
417 version = python_version()
418 plat = version[0] if version[0] == "win32" else version[1]
419 if version[1] == '64bit' and version[0] == 'win32':
420 plat = "amd64"
421 cp = "-cp%d%d-" % sys.version_info[:2]
422 py = "-py%d%d-" % sys.version_info[:2]
423 pyn = "-py%d-" % sys.version_info[0]
424 links = [_ for _ in alls if "/" +
425 self.name in _ and (pyn in _ or py in _ or cp in _) and (plat in _ or "-any" in _)]
426 if len(links) == 0 and "-" in self.name:
427 name_ = self.name.replace("-", "_")
428 links = [_ for _ in alls if "/" +
429 name_ in _ and (pyn in _ or py in _ or cp in _) and (plat in _ or "-any" in _)]
430 if len(links) == 0:
431 # exception for guidata
432 links = [_ for _ in alls if self.name in _ and "-py2.py3-" in _]
433 if len(links) == 0:
434 if file_save is not None:
435 with open(file_save, "w", encoding="utf8") as f:
436 f.write(page)
437 short_list = [_ for _ in alls if self.name in _]
438 raise MissingWheelException("unable to find a single link for " +
439 self.name + "\n" + "\n".join(short_list))
441 # Last filter. Removes modules with a longer name.
442 pref1 = self.name.lower() + "-"
443 pref2 = self.name.lower().replace("-", "_") + "-"
444 pref3 = self.name.lower().replace("_", "-") + "-"
446 def filter_cond(name):
447 name = name.split("/")[-1].lower()
448 return name.startswith(pref1) or name.startswith(pref2) or name.startswith(pref3)
450 links_ = [_ for _ in links if filter_cond(_)]
451 if len(links_) == 0:
452 prefs = "\npref1={0}\npref2={1}\npref3={2}".format(
453 pref1, pref2, pref3)
454 raise MissingWheelException("unable to find a single link for " +
455 self.name + "\n" + "\n".join(links) + prefs)
456 links = links_
458 links = [(lu.split("/")[-1], lu) for lu in links]
459 links0 = links
461 if self.name == "numpy":
462 links = [lu for lu in links if "unoptimized" not in lu[
463 0].lower() and "vanilla" not in lu[0].lower()]
465 if len(links) == 0:
466 raise Exception("unable to find a single link for " +
467 self.name +
468 "\nEX:\n" +
469 "\n".join(str(_) for _ in links0))
471 link = choose_most_recent(links)
472 self.existing_version = self.extract_version(link[0])
473 url, whl = link[1], link[0]
474 if not whl.endswith(".whl"):
475 whl += ".whl"
476 return url, whl
478 _page_cache_html = os.path.join(
479 os.path.abspath(os.path.split(__file__)[0]), "page.html")
481 def get_exewheel_url_link(self, file_save=None, wheel=False):
482 """
483 for windows, get the url of the setup using a webpage
485 @param file_save for debug purposes
486 @param wheel returns the wheel file or the exe file
487 @return url, exe name
488 """
489 if "cached_page" not in self.__dict__:
490 page = ModuleInstall._page_cache_html
492 exi = os.path.exists(page)
493 if exi:
494 dt = get_file_modification_date(page)
495 now = datetime.datetime.now()
496 df = now - dt
497 if df > datetime.timedelta(1):
498 exi = False
500 if exi:
501 text = read_page_wheel(page)
502 self.cached_page = text
503 else:
504 text = get_page_wheel(ModuleInstall.exeLocation)
505 save_page_wheel(page, text)
506 self.cached_page = text
508 page = self.cached_page
509 alls = extract_all_links(page)
510 if len(alls) == 0:
511 keep = []
512 for line in page.split("\n"):
513 lline = line.lower()
514 if self.name in lline or (self.mname and self.mname in lline):
515 keep.append(line)
516 raise Exception(
517 "module " +
518 self.name + "\nexample:\n" + "\n".join(keep))
520 version = python_version()
521 plat = version[0] if version[0] == "win32" else version[1]
522 if version[1] == '64bit' and version[0] == 'win32':
523 plat = "amd64"
524 links = list(enumerate_links_module(
525 self.name, alls, sys.version_info, plat))
526 if len(links) == 0:
527 if file_save is not None:
528 with open(file_save, "w", encoding="utf8") as f:
529 f.write(page)
530 raise MissingWheelException(
531 "Unable to find a single link for " + self.name)
532 nbnone = [lu for lu in links if lu[2] is None]
533 if len(nbnone) * 2 > len(links):
534 raise WrongWheelException("Unable to find any version in\n{0}".format(
535 "\n".join(str(_) for _ in links)))
536 links0 = links
538 if self.name == "numpy":
539 links = [lu for lu in links if "unoptimized" not in lu[
540 0].lower() and "vanilla" not in lu[0].lower()]
542 if len(links) == 0:
543 raise Exception("unable to find a single link for " +
544 self.name +
545 "\nEX:\n" +
546 "\n".join(str(_) for _ in links0))
548 link = choose_most_recent(links)
549 self.existing_version = self.extract_version(link[0])
550 if link[2] is None:
551 raise WrongWheelException("Unable to find a proper link in {0}\n{1}".format(
552 link, "\n".join(str(_) for _ in links)))
553 url, whl = ModuleInstall.exeLocation + link[2], link[0]
554 if not whl.endswith(".whl"):
555 whl += ".whl"
556 return url, whl
558 def unzipfiles(self, zipf, whereTo):
559 """
560 unzip files from a zip archive
562 @param zipf archive
563 @param whereTo destination folder
564 @return list of unzipped files
565 """
566 return unzip_files(zipf, whereTo, self.fLOG)
568 def extract_version(self, name):
569 """
570 extract the version from a filename
572 @param name filename
573 @return verions (str)
574 """
575 res = None
576 for i, regve in enumerate(regex_wheel_versions):
577 reg = re.compile(regve)
578 res = reg.search(name)
579 if res is not None:
580 if i == 6:
581 return ".".join(res.groups())
582 else:
583 return res.groups()[0]
584 raise MissingVersionWheelException(
585 "Unable to extract version number from '{0}'\nREGEX\n{1}".format(name, "\n".join(regex_wheel_versions)))
587 def download(self, temp_folder=".", force=False, unzipFile=True,
588 file_save=None, deps=False, source=None):
589 """
590 Downloads the module without installation.
592 @param temp_folder destination
593 @param force force the installation even if already installed
594 @param unzipFile if it can be unzipped, it will be (for github, mostly)
595 @param file_save for debug purposes, do not change it unless you know what you are doing
596 @param deps download the dependencies too (only available for pip)
597 @param source overwrite source of the download, only for wheel packages,
598 see @see me get_exewheel_url_link2
599 @return downloaded files
601 *deps* is overwritten by *self.deps* if not None
602 If *source* is None, it is overwritten by *self.source*.
603 """
604 disable_options = {}
605 if source is None:
606 source = self.source
607 kind = self.kind
609 deps = deps if self.deps is None else self.deps
611 if kind == "pip" or self.pipgit:
612 # see https://pip.pypa.io/en/latest/reference/pip_install.html
613 # we use pip install <package> --download=temp_folder
614 pp = get_pip_program()
615 if self.pipgit:
616 br = "@" + \
617 self.branch if self.branch not in (None, "master") else ""
618 link = ModuleInstall.github_pattern_git.format(
619 self.name, self.gitrepo, br)
620 cmd = pp + ' download git+' + link
621 else:
622 cmd = pp + ' download {0}'.format(self.name)
623 if self.version is not None:
624 cmd += "=={0}".format(self.version)
625 if " " in temp_folder:
626 raise FileNotFoundError(
627 "no space allowed in folders: [" + temp_folder + "]")
628 if deps:
629 cmd += ' --dest={0}'.format(temp_folder)
630 else:
631 cmd += ' --dest={0} --no-deps'.format(temp_folder)
633 if self.index_url is not None:
634 slash = '' if self.index_url.endswith('/') else '/'
635 cmd += ' --no-cache-dir --index={0}{1}simple/'.format(
636 self.index_url, slash)
637 parsed_uri = urlsplit(self.index_url)
638 cmd += " --trusted-host " + parsed_uri.hostname
639 if self.pip_options is not None:
640 diff = [_ for _ in self.pip_options if _ not in disable_options]
641 cmd += " " + " ".join(diff)
643 out, err = run_cmd(
644 cmd, wait=True, fLOG=self.fLOG)
645 if "Successfully downloaded" not in out:
646 raise DownloadError(
647 "unable to download with pip " +
648 str(self) + "\nCMD:\n" +
649 cmd + "\nOUT:\n" +
650 out + "\nERR-N:\n" +
651 err)
652 lines = out.split("\n")
653 for line in lines:
654 if line.strip().startswith("Saved "):
655 return line.split("Saved")[-1].strip()
656 elif line.strip().startswith("File was already downloaded"):
657 return line.split("File was already downloaded")[-1].strip()
658 raise FileNotFoundError(
659 "unable to find downloaded file " +
660 str(self) + "\nCMD:\n" +
661 cmd + "\nOUT:\n" +
662 out + "\nERR-O:\n" +
663 err)
665 if kind in ("wheel", "wheel2"):
666 if source is not None:
667 kind = "wheel2"
668 ver = python_version()
669 if ver[0] != "win32":
670 # nothing to download, you should use pip
671 return None
672 else:
673 if hasattr(self, "overwrite") and self.overwrite is not None:
674 over = self.overwrite.format(*sys.version_info[0:2])
675 url, whl = over, over.split("/")[-1]
676 self.existing_version = "{0}{1}".format(
677 *sys.version_info[0:2])
678 elif kind == "wheel":
679 url, whl = self.get_exewheel_url_link(
680 file_save=file_save, wheel=True)
681 else:
682 url, whl = self.get_exewheel_url_link2(
683 file_save=file_save, wheel=True, source=source)
684 whlname = os.path.join(temp_folder, whl)
686 exi = os.path.exists(whlname)
687 if force or not exi:
689 self.fLOG("[pymy] downloading", whl)
690 # self.fLOG("url", url)
691 if self.existing_version is None:
692 self.existing_version = self.extract_version(whl)
693 req = urllib_request.Request(
694 url, headers={
695 'User-agent': default_user_agent})
696 try:
697 u = urllib_request.urlopen(req)
698 text = u.read()
699 u.close()
700 except urllib_error.HTTPError as e:
701 raise DownloadError("unable to download {} from {}".format(
702 os.path.split(whlname)[-1], url)) from e
704 if not os.path.exists(temp_folder):
705 os.makedirs(temp_folder)
707 if len(text) <= 1000:
708 raise WrongWheelException("Size of downloaded wheel is too small: {0}\nurl={1}\nagent={2}".format(
709 len(text), url, default_user_agent))
711 self.fLOG("[pymy] writing", whl)
712 with open(whlname, "wb") as f:
713 f.write(text)
715 return whlname
717 elif kind == "github" and not self.pipgit:
718 outfile = os.path.join(temp_folder, self.name + ".zip")
719 if force or not os.path.exists(outfile):
720 zipurl = ModuleInstall.github_pattern_zip.format(
721 self.name, self.gitrepo, self.branch)
722 self.fLOG("[pymy] downloading", zipurl)
723 try:
724 req = urllib_request.Request(
725 zipurl, headers={
726 'User-agent': default_user_agent})
727 u = urllib_request.urlopen(req)
728 text = u.read()
729 u.close()
730 except urllib_error.HTTPError as e:
731 raise Exception(
732 "unable to get archive from: " +
733 zipurl) from e
735 if not os.path.exists(temp_folder):
736 os.makedirs(temp_folder)
737 u = open(outfile, "wb")
738 u.write(text)
739 u.close()
741 if unzipFile:
742 self.fLOG("[pymy] unzipping ", outfile)
743 files = self.unzipfiles(outfile, temp_folder)
744 return files
745 else:
746 return outfile
748 if kind in ("exe", "exe2"):
749 if source is not None:
750 kind = "exe2"
751 ver = python_version()
752 if ver[0] != "win32":
753 raise Exception(
754 "this option is not available on other systems than Windows, version={0}".format(ver))
755 url, exe = self.get_exewheel_url_link(
756 file_save=file_save) if kind == "exe" else self.get_exewheel_url_link2(
757 file_save=file_save, source=source)
759 self.fLOG("[pymy] downloading", exe)
760 req = urllib_request.Request(
761 url, headers={
762 'User-agent': default_user_agent})
763 u = urllib_request.urlopen(req)
764 text = u.read()
765 u.close()
767 if not os.path.exists(temp_folder):
768 os.makedirs(temp_folder)
770 exename = os.path.join(temp_folder, exe)
771 self.fLOG("[pymy] writing", exe)
772 with open(exename, "wb") as f:
773 f.write(text)
774 return exename
776 raise ImportError(
777 "unknown kind: {0} for module {1}".format(
778 kind,
779 self.name))
781 def get_pypi_version(self, url='https://pypi.python.org/pypi'):
782 """
783 returns the version of a package on pypi
785 @param url pipy server
786 @return version
788 See also `installing_python_packages_programatically.py <https://gist.github.com/rwilcox/755524>`_,
789 `pkgtools.pypi: PyPI interface <http://pkgtools.readthedocs.org/en/latest/pypi.html>`_.
790 """
791 if url == 'https://pypi.python.org/pypi':
792 # we use a function which caches the result
793 return get_pypi_version(self.name)
794 else:
795 pypi = xmlrpc_client.ServerProxy(url)
796 available = pypi.package_releases(self.name)
797 if available is None or len(available) == 0:
798 available = pypi.package_releases(self.name.capitalize())
799 if (available is None or len(available) == 0) and self.mname is not None:
800 available = pypi.package_releases(self.mname)
802 if available is None:
803 raise MissingPackageOnPyPiException(
804 "; ".join([self.name, self.name.capitalize(), self.mname]))
806 return available[0]
808 def get_pypi_numeric_version(self):
809 """
810 returns the version of a package in pypi
812 @return tuple
813 """
814 vers = self.get_pypi_version()
815 if vers is None:
816 return None
817 if isinstance(vers, list):
818 v = self.get_pypi_version()
819 raise TypeError("unexpected type: {0} -- {1}".format(vers, v))
820 return numeric_version(vers)
822 def get_installed_version(self):
823 """
824 return the version of the installed package
826 @return version
827 """
828 vers = get_module_version(None)
829 if self.name in vers:
830 return vers[self.name]
831 cap = self.name.capitalize()
832 if cap in vers:
833 return vers[cap]
834 cap = self.name.lower()
835 if cap in vers:
836 return vers[cap]
837 cap = self.name.replace("-", "_")
838 if cap in vers:
839 return vers[cap]
840 cap = self.name.replace("_", "-")
841 if cap in vers:
842 return vers[cap]
843 cap = self.name.lower().replace("_", "-")
844 if cap in vers:
845 return vers[cap]
846 if self.mname is not None:
847 if self.mname in vers:
848 return vers[self.mname]
849 cap = self.mname.lower()
850 if cap in vers:
851 return vers[cap]
852 return None
854 def get_installed_metadata(self):
855 """
856 return the metadata of the installed package
858 @return dictionary
859 """
860 r = get_module_metadata(self.name)
861 if r is None:
862 return get_module_metadata(self.mname)
863 else:
864 return r
866 def get_installed_license(self):
867 """
868 return the license of the installed package
870 @return string
871 """
872 meta = self.get_installed_metadata()
873 if meta is None or len(meta) == 0:
874 res = None
875 else:
876 res = None
877 for k, v in meta.items():
878 if k.lower() == "license":
879 res = v
880 break
881 adm = {None, "", "UNKNOWN"}
882 if res is not None:
883 if isinstance(res, list):
884 res = [_ for _ in res if _ and _ not in adm]
885 if len(res) > 0:
886 res = res[0]
887 else:
888 res = None
889 if res in adm:
890 res = missing_module_licenses.get(self.name, None)
891 return res
893 def get_installed_classifier(self):
894 """
895 return the classifier of the installed package
897 @return string
898 """
899 meta = self.get_installed_metadata()
900 if meta is None:
901 return None
902 for k, v in meta.items():
903 if k.lower() == "classifier":
904 return v
905 return None
907 def is_installed_version(self):
908 """
909 tells if a module is installed
911 @return boolean
912 """
913 return self.get_installed_version() is not None
915 def get_installed_numeric_version(self):
916 """
917 returns the version as number (not string)
919 @return tuple
920 """
921 vers = self.get_installed_version()
922 if vers is None:
923 return None
924 return numeric_version(vers)
926 def has_update(self):
927 """
928 tells if the package has a newer version on pipy
930 @return boolean
931 """
932 if ModuleInstall.is_annoying(self.name):
933 return False
934 vers = self.get_installed_numeric_version()
935 if self.version is None:
936 pypi = self.get_pypi_numeric_version()
937 return compare_version(pypi, vers) > 0
938 else:
939 num = numeric_version(self.version)
940 return compare_version(num, vers) > 0
942 def _check_installation(self):
943 """
944 some modules uninstall and install modules with another version number,
945 we try to track that
946 """
947 try:
948 import numpy
949 if compare_version(numpy.__version__, "1.10") < 0:
950 raise InstallError(
951 "numpy does not have a goof version number, it should be >= 1.10 not {0}".format(numpy.__version__))
952 except ImportError:
953 # not installed
954 pass
955 return True
957 def install(self, force_kind=None, force=False, temp_folder=".",
958 log=False, options=None, deps=False, source=None,
959 custom=None, post=None, out_streams=None):
960 """
961 Installs the package.
963 @param force_kind overwrite self.kind
964 @param force force the installation even if already installed
965 @param temp_folder folder where to download the setup
966 @param log display logs or not
967 @param options other options to add to the command line (see below) in a list
968 @param deps install the dependencies too (only available for pip)
969 @param source overwrite the source of the wheels,
970 see @see me get_exewheel_url_link2
971 @param custom overwrite parameters in ``self.custom``
972 @param post instructions post installation (see the cnostructor for more help)
973 @param out_streams if it is a list, the function will add standard outputs
974 @return boolean
976 The options mentioned in parameter ``options``
977 are described here: `pip install <http://www.pip-installer.org/en/latest/usage.html>`_
978 or `setup.py options <http://docs.python.org/3.4/install/>`_ if you
979 installing a module from github.
981 .. versionchanged:: 1.0
982 *deps* is overwritten by *self.deps* if not None
984 .. versionchanged:: 1.1
985 On Anaconda (based on function @see fn is_conda_distribution), we try *conda* first
986 before switching to the regular way if it did not work.
987 Exception were changed from ``Exception`` to ``InstallError``.
988 Parameter *source* was added, if None, it is overwritten by *self.source*.
989 Parameter *custom* was added, it works the same as *source*.
990 Parameter *post* was added.
991 Parameter *out_streas* added.
992 """
993 if source is None:
994 source = self.source
995 if post is None:
996 post = self.post_installation
997 if not force and force_kind is None and is_conda_distribution():
998 try:
999 return self.install(force_kind="conda", force=True, temp_folder=temp_folder,
1000 log=log, options=options, deps=deps)
1001 except InstallError as e:
1002 warnings.warn(str(e))
1003 # we try the regular way now
1005 if not force and self.is_installed_version():
1006 return True
1008 deps = deps if self.deps is None else self.deps
1010 if options is None:
1011 options = self.pip_options
1012 if options is None:
1013 options = []
1015 kind = force_kind if force_kind is not None else self.kind
1016 add = (" with " + kind) if kind != self.kind else ""
1017 self.fLOG("[pymy] installation of " + str(self) + add)
1018 ret = None
1019 custom = custom or self.custom
1021 if kind == "pip" or self.pipgit:
1022 if custom is not None:
1023 raise NotImplementedError(
1024 "custom must be None not '{0}' when kind is '{1}'".format(custom, kind))
1025 pp = get_pip_program()
1026 if self.pipgit:
1027 br = "@" + \
1028 self.branch if self.branch not in (None, "master") else ""
1029 link = ModuleInstall.github_pattern_git.format(
1030 self.name, self.gitrepo, br)
1031 cmd = pp + ' install git+' + link
1032 else:
1033 cmd = pp + " install {0}".format(self.name)
1034 if self.version is not None:
1035 cmd += "=={0}".format(self.version)
1036 if len(options) > 0:
1037 cmd += " " + " ".join(options)
1039 if not deps:
1040 cmd += ' --no-deps'
1042 if self.index_url is not None:
1043 slash = '' if self.index_url.endswith('/') else '/'
1044 cmd += ' --no-cache-dir --index={0}{1}simple/'.format(
1045 self.index_url, slash)
1046 else:
1047 cmd += " --cache-dir={0}".format(temp_folder)
1049 if self.name == "kivy-garden":
1050 memo = sys.argv
1051 sys.argv = []
1052 out, err = run_cmd(
1053 cmd, wait=True, fLOG=self.fLOG)
1054 out = _filter_pip_out(out)
1055 if out_streams is not None:
1056 out_streams.append((cmd, out, err))
1057 if self.name == "kivy-garden":
1058 sys.argv = memo
1060 success2 = "Requirement already up-to-date: " + self.name
1061 uptodate = success2.replace("-", "_") in out.replace("-", "_")
1063 if "No distributions matching the version" in out:
1064 mes = "(1) unable to install with pip {0}\nCMD:\n{1}\nOUT:\n{2}\nERR-P:\n{3}".format(
1065 str(self), cmd, out, err)
1066 raise InstallError(mes)
1067 if "Testing of typecheck-decorator passed without failure." in out:
1068 ret = True
1069 elif "Successfully installed" not in out and not uptodate:
1070 if "error: Unable to find vcvarsall.bat" in out:
1071 url = "http://www.xavierdupre.fr/blog/2013-07-07_nojs.html"
1072 mes = "(2) unable to install with pip {0}\nread:\n{1}\nCMD:\n{2}\nOUT:\n{3}\nERR-P:\n{4}".format(
1073 str(self), url, cmd, out, err)
1074 raise InstallError(mes)
1075 if "Requirement already satisfied" not in out and not uptodate:
1076 mes = "(3) unable to install with pip {0}\nCMD:\n{1}\nOUT:\n{2}\nERR-Q:\n{3}".format(
1077 str(self), cmd, out, err)
1078 raise InstallError(mes)
1079 else:
1080 ret = not uptodate
1082 elif kind == "conda":
1083 if custom is not None:
1084 raise NotImplementedError(
1085 "custom must be None not '{0}' when kind is '{1}'".format(custom, kind))
1086 if "--upgrade" in options:
1087 options = [_ for _ in options if _ != "--upgrade"]
1088 command = "update"
1089 else:
1090 command = "install"
1091 pp = get_conda_program()
1092 cmd = pp + " {1} {0}".format(self.name, command)
1093 if self.version is not None:
1094 cmd += "=={0}".format(self.version)
1095 if len(options) > 0:
1096 cmd += " " + " ".join(options)
1098 cmd += " --no-pin --yes --quiet"
1099 if not deps:
1100 cmd += ' --no-deps'
1102 out, err = run_cmd(
1103 cmd, wait=True, fLOG=self.fLOG)
1104 if out_streams is not None:
1105 out_streams.append((cmd, out, err))
1106 if "No distributions matching the version" in out or \
1107 "No packages found in current linux" in out:
1108 mes = "(4) unable to install with conda {0}\nCMD:\n{1}\nOUT:\n{2}\nERR-R:\n{3}".format(
1109 str(self), cmd, out, err)
1110 raise InstallError(mes)
1111 if "Testing of typecheck-decorator passed without failure." in out:
1112 ret = True
1113 elif "Successfully installed" not in out:
1114 if "error: Unable to find vcvarsall.bat" in out:
1115 url = "http://www.xavierdupre.fr/blog/2013-07-07_nojs.html"
1116 mes = "(5) unable to install with conda {0}\nread:\n{1}\nCMD:\n{2}\nOUT:\n{3}\nERR-S:\n{4}".format(
1117 str(self), url, cmd, out, err)
1118 raise InstallError(mes)
1119 if "Requirement already satisfied" not in out:
1120 mes = "(6) unable to install with conda {0}\nCMD:\n{1}\nOUT:\n{2}\nERR-T:\n{3}".format(
1121 str(self), cmd, out, err)
1122 raise InstallError(mes)
1123 else:
1124 ret = True
1126 elif kind in ("wheel", "wheel2"):
1127 if custom is not None:
1128 raise NotImplementedError(
1129 "custom must be None not '{0}' when kind is '{1}'".format(custom, kind))
1130 ver = python_version()
1131 if ver[0] != "win32":
1132 ret = self.install("pip")
1133 whlname = self.name
1134 ret = True
1135 else:
1136 whlname = self.download(
1137 temp_folder=temp_folder,
1138 force=force,
1139 unzipFile=True,
1140 source=source)
1141 vers = self.get_installed_numeric_version()
1142 ret = True
1143 if vers is not None:
1144 whlvers = numeric_version(get_wheel_version(whlname))
1145 if compare_version(vers, whlvers) >= 0:
1146 self.fLOG("[pymy] skipping, no newer version {0} <= {1}: whl= {2}".format(
1147 whlvers, vers, whlname))
1148 ret = False
1149 if ret:
1150 self.fLOG("[pymy] installing", os.path.split(whlname)[-1])
1152 pip = get_pip_program()
1153 cmd = pip + " install {0}".format(whlname)
1154 if self.version is not None:
1155 cmd += "=={0}".format(self.version)
1156 if len(options) > 0:
1157 opts = list(options)
1158 if len(opts):
1159 cmd += " " + " ".join(opts)
1160 if not deps:
1161 cmd += ' --no-deps'
1163 out, err = run_cmd(
1164 cmd, wait=True, fLOG=self.fLOG)
1165 if out_streams is not None:
1166 out_streams.append((cmd, out, err))
1167 if "No distributions matching the version" in out:
1168 mes = "(7) unable to install with wheel {0}\nCMD:\n{1}\nOUT:\n{2}\nERR-U:\n{3}".format(
1169 str(self), cmd, out, err)
1170 raise InstallError(mes)
1171 if "Testing of typecheck-decorator passed without failure." in out:
1172 ret = True
1173 elif "Successfully installed" not in out:
1174 if "error: Unable to find vcvarsall.bat" in out:
1175 url = "http://www.xavierdupre.fr/blog/2013-07-07_nojs.html"
1176 mes = "(8) unable to install with wheel {0}\nread:\n{1}\nCMD:\n{2}\nOUT:\n{3}\nERR-V:\n{4}".format(
1177 str(self), url, cmd, out, err)
1178 raise InstallError(mes)
1179 if "Requirement already satisfied" not in out and "Requirement already up-to-date" not in out:
1180 mes = "(9) unable to install with wheel {0}\nCMD:\n{1}\nOUT:\n{2}\nERR-W:\n{3}".format(
1181 str(self), cmd, out, err)
1182 raise InstallError(mes)
1183 else:
1184 ret = True
1186 elif kind == "github" and not self.pipgit:
1187 # the following code requires admin rights
1188 # if python_version()[0].startswith("win") and kind == "git" and not os.path.exists(ModuleInstall.gitexe) :
1189 # raise FileNotFoundError("you need to install github first: see http://windows.github.com/")
1190 # if python_version()[0].startswith("win"):
1191 # os.chdir(os.path.join(ModuleInstall.gitexe,"bin"))
1192 # cmd = pip + " install -e
1193 # git://github.com/{0}/{1}-python.git#egg={1}".format(self.gitrepo,
1194 # self.name)
1196 files = self.download(temp_folder=temp_folder,
1197 force=force, unzipFile=True)
1198 setu = [_ for _ in files if _.endswith("setup.py")]
1199 if len(setu) == 0:
1200 raise Exception(
1201 "unable to find setup.py for module " +
1202 self.name)
1203 if len(setu) > 1:
1204 setu = [(len(_), _) for _ in setu]
1205 setu.sort()
1206 if setu[0][0] == setu[1][0]:
1207 raise InstallError(
1208 "(10) more than one setup.py for module " +
1209 self.name +
1210 "\n" +
1211 "\n".join(
1212 str(_) for _ in setu))
1213 self.fLOG(
1214 "[pymy] warning: more than one setup: " + str(setu))
1215 setu = [setu[0][1]]
1216 setu = os.path.abspath(setu[0])
1218 self.fLOG("[pymy] install ", setu[0])
1219 cwd = os.getcwd()
1220 os.chdir(os.path.split(setu)[0])
1221 if custom is None:
1222 custom = ["install"]
1223 cmds = []
1224 for command in custom:
1225 cmd1 = "{0} setup.py {1}".format(
1226 sys.executable.replace("pythonw.exe", "python.exe"),
1227 command)
1228 cmds.append(cmd1)
1230 def enumerate_filtered_option(options):
1231 for o in options:
1232 if o not in ('--no-deps', '--upgrade'):
1233 yield o
1235 filter_options = list(enumerate_filtered_option(options))
1236 if len(filter_options) > 0:
1237 cmds[-1] += " " + " ".join(filter_options)
1239 if deps:
1240 # it will not work
1241 # cmd += ' --no-deps'
1242 pass
1244 outs = ""
1245 errs = ""
1246 for cmd in cmds:
1247 out, err = run_cmd(
1248 cmd, wait=True, fLOG=self.fLOG)
1249 if out_streams is not None:
1250 out_streams.append((cmd, out, err))
1251 if len(outs) > 0:
1252 outs += "\n"
1253 if len(errs) > 0:
1254 errs += "\n"
1255 outs += out
1256 errs += err
1257 os.chdir(cwd)
1259 out, err = outs, errs
1260 if "Successfully installed" not in out and "install C" not in out:
1261 if "Finished processing dependencies" not in out:
1262 raise InstallError(
1263 "(11) unable to install with github " +
1264 str(self) +
1265 "\nOUT:\n" +
1266 out +
1267 "\nERR-X:\n" +
1268 err)
1269 self.fLOG(
1270 "warning: ``Successfully installed`` or ``install C`` not found")
1271 if "Permission denied" in out:
1272 raise PermissionError(" ".join(["(12) unable to install with github", str(self), "\n--CMD--:\n",
1273 "\n".join(cmds), "\n--OUT--\n", out, "\n--ERR-Y--\n", err]))
1274 ret = True
1276 elif kind == "exe":
1277 if custom is not None:
1278 raise NotImplementedError(
1279 "custom must be None not '{0}' when kind is '{1}'".format(custom, kind))
1280 ver = python_version()
1281 if ver[0] != "win32":
1282 ret = self.install("pip")
1283 else:
1284 exename = self.download(
1285 temp_folder=temp_folder,
1286 force=force,
1287 unzipFile=True,
1288 source=source)
1289 self.fLOG("[pymy] executing", os.path.split(exename)[-1])
1290 out, err = run_cmd(
1291 exename + " /s /qn /SILENT", wait=True, fLOG=self.fLOG)
1292 if out_streams is not None:
1293 out_streams.append((cmd, out, err))
1294 ret = len(err) == 0
1296 elif kind == "exe2":
1297 if custom is not None:
1298 raise NotImplementedError(
1299 "custom must be None not '{0}' when kind is '{1}'".format(custom, kind))
1300 ver = python_version()
1301 if ver[0] != "win32":
1302 ret = self.install("pip")
1303 else:
1304 exename = self.download(
1305 temp_folder=temp_folder,
1306 force=force,
1307 unzipFile=True,
1308 source=source)
1309 self.fLOG("[pymy] executing", os.path.split(exename)[-1])
1310 out, err = run_cmd(
1311 exename + " /s /qn", wait=True, fLOG=self.fLOG)
1312 if out_streams is not None:
1313 out_streams.append((cmd, out, err))
1314 ret = len(err) == 0
1315 else:
1316 raise ImportError(
1317 "unknown kind: {0} for module {1}".format(
1318 kind,
1319 self.name))
1321 if ret is not None and ret:
1322 self._check_installation()
1324 # at this stage, there is a bug, for some executable, the execution
1325 # takes longer than expected
1326 # if not self.is_installed_version() :
1327 # raise Exception("unable to install module: {0}, str:{1}".format(self.name, self))
1329 if ret is not None and ret and self.script is not None:
1330 if sys.platform.startswith("win"):
1331 # here, we have to wait until the script is installed
1332 ti = 0
1333 while not self.is_installed_local():
1334 time.sleep(0.5)
1335 ti += 0.5
1336 if ti > 60:
1337 self.fLOG(
1338 "wait for the end of execution of ",
1339 self.name)
1340 ti = 0
1342 # we add set path=%path%;PYTHONPATH
1343 with open(self.Script, "r") as f:
1344 full = f.read()
1346 exe = os.path.split(sys.executable)[0]
1347 cmd = "set path=%path%;{0}".format(exe)
1348 if cmd not in full:
1349 self.fLOG("[pymy] add {0} to {1}".format(cmd, self.Script))
1350 with open(self.Script, "w") as f:
1351 if full.startswith("@echo off"):
1352 f.write(
1353 full.replace(
1354 "@echo off",
1355 "@echo off\n{0}".format(cmd)))
1356 else:
1357 f.write(cmd)
1358 f.write("\n")
1359 f.write(full)
1361 if ret is not None and post is not None:
1362 self.fLOG("[pymy] _ run_post_installation [begin]", post)
1363 ret = self.run_post_installation(post)
1364 self.fLOG("[pymy] _ run_post_installation [end]")
1366 if ret is not None and ret:
1367 # we check the module was properly installed
1368 if not self.is_installed_local_cmd():
1369 raise InstallError(
1370 "** unable to install module {0}, unable to import it".format(self.name))
1372 return ret
1374 def run_post_installation(self, post):
1375 """
1376 Run instructions post installation
1378 @param post dictionary, instructions post installation
1379 @return boolean
1381 Example:
1383 ::
1385 post = dict(
1386 cmd_python="Scripts\\\\pywin32_postinstall.py -install")
1388 .. versionadded:: 1.1
1389 """
1390 if not isinstance(post, dict):
1391 raise TypeError("expecting a dictionary")
1392 for k, v in post.items():
1393 if k == "cmd_python":
1394 # processed just above
1395 continue
1396 if k == "pre_cmd":
1397 if v == "module_install_preprocess":
1398 self.fLOG("[pymy] _ module_install_preprocess [begin]")
1399 self.module_install_preprocess(post)
1400 self.fLOG("[pymy] _ module_install_preprocess [end]")
1401 else:
1402 raise ValueError(
1403 "Unable to interpret value '{0}'.".format(v))
1404 else:
1405 raise KeyError("Unable to interpret command '{0}'".format(k))
1406 for k, v in post.items():
1407 if k == "cmd_python":
1408 exe = os.path.abspath(sys.executable)
1409 cmd = '"{0}" {1}'.format(exe, v.format(os.path.dirname(exe)))
1410 out, err = run_cmd(cmd, wait=True, fLOG=self.fLOG)
1411 if err is not None and len(err) > 0:
1412 raise InstallError(
1413 "Post installation script failed.\nCMD\n{0}\nOUT\n{1}\nERR-Z\n{2}".format(cmd, out, err))
1414 self.fLOG("[pymy] OUT:\n{0}".format(out))
1415 elif k == "pre_cmd":
1416 # processed just above
1417 continue
1418 else:
1419 raise KeyError("Unable to interpret command '{0}'".format(k))
1420 return True
1422 def update(self, force_kind=None, force=False, temp_folder=".",
1423 log=False, options=None, deps=False, source=None):
1424 """
1425 Updates the package if necessary, we use
1426 ``pip install <module_name> --upgrade --no-deps``,
1428 @param force_kind overwrite self.kind
1429 @param force force the installation even if not need to update
1430 @param temp_folder folder where to download the setup
1431 @param log display logs or not
1432 @param options others options to add to the command line (see below) an a list
1433 @param deps download the dependencies too (only available for pip)
1434 @param source overwrite the source of the wheel, see @see me get_exewheel_url_link2
1435 @return boolean
1437 The options mentioned in parameter ``options``
1438 are described here: `pip install <http://www.pip-installer.org/en/latest/usage.html>`_
1439 or `setup.py options <http://docs.python.org/3.4/install/>`_ if you
1440 installing a module from github.
1442 .. versionchanged:: 1.1
1443 Parameter *source* was added, if None, it is overwritten by *self.source*.
1444 """
1445 if source is None:
1446 source = self.source
1447 if ModuleInstall.is_annoying(self.name):
1448 return False
1450 if not self.is_installed_version():
1451 raise MissingInstalledPackageException(self.name)
1453 if not force and not self.has_update():
1454 return True
1456 self.fLOG("[pymy] update of ", self)
1458 options = [] if options is None else list(options)
1459 for opt in ["--upgrade", "--no-deps"]:
1460 if opt not in options:
1461 if not deps or opt == "--no-deps":
1462 options.append(opt)
1464 res = self.install(force_kind=force_kind, force=True,
1465 temp_folder=temp_folder, log=log, options=options, source=source)
1466 return res
1468 def module_install_preprocess(self, post):
1469 """
1470 Run some preprocessing for specific modules
1472 @param post dictionary
1473 """
1474 if self.name == "pywin32":
1475 self.fLOG("[pymy] _ module_install_preprocess", self.name)
1476 if "cmd_python" not in post:
1477 raise KeyError("Key 'cmd_python' is not in post.")
1478 cmd_python = post["cmd_python"]
1479 name = cmd_python.split()[0].format(
1480 os.path.dirname(sys.executable))
1481 self.fLOG("[pymy] _ opening", name)
1482 if not os.path.exists(name):
1483 raise FileNotFoundError(name)
1484 with open(name, "r") as f:
1485 content = f.read()
1486 repby = "os.path.exists(dest)"
1487 if repby not in content:
1488 self.fLOG("[pymy] _ Preprocess '{0}'".format(name))
1489 rep = "def CopyTo(desc, src, dest):"
1490 content = content.replace(
1491 rep, "{0}\n if {1}: return".format(rep, repby))
1492 with open(name, "w") as f:
1493 f.write(content)
1494 self.fLOG("[pymy] _ Done.")
1495 else:
1496 self.fLOG(
1497 "Skip preprocess '{0}' because '{1}' was found.".format(name, repby))
1498 else:
1499 raise ValueError(
1500 "No preprocessing for module '{0}'.".format(self.name))