_virtualenv.py 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130
  1. """Patches that are applied at runtime to the virtual environment"""
  2. # -*- coding: utf-8 -*-
  3. import os
  4. import sys
  5. VIRTUALENV_PATCH_FILE = os.path.join(__file__)
  6. def patch_dist(dist):
  7. """
  8. Distutils allows user to configure some arguments via a configuration file:
  9. https://docs.python.org/3/install/index.html#distutils-configuration-files
  10. Some of this arguments though don't make sense in context of the virtual environment files, let's fix them up.
  11. """
  12. # we cannot allow some install config as that would get packages installed outside of the virtual environment
  13. old_parse_config_files = dist.Distribution.parse_config_files
  14. def parse_config_files(self, *args, **kwargs):
  15. result = old_parse_config_files(self, *args, **kwargs)
  16. install = self.get_option_dict("install")
  17. if "prefix" in install: # the prefix governs where to install the libraries
  18. install["prefix"] = VIRTUALENV_PATCH_FILE, os.path.abspath(sys.prefix)
  19. for base in ("purelib", "platlib", "headers", "scripts", "data"):
  20. key = "install_{}".format(base)
  21. if key in install: # do not allow global configs to hijack venv paths
  22. install.pop(key, None)
  23. return result
  24. dist.Distribution.parse_config_files = parse_config_files
  25. # Import hook that patches some modules to ignore configuration values that break package installation in case
  26. # of virtual environments.
  27. _DISTUTILS_PATCH = "distutils.dist", "setuptools.dist"
  28. if sys.version_info > (3, 4):
  29. # https://docs.python.org/3/library/importlib.html#setting-up-an-importer
  30. from functools import partial
  31. from importlib.abc import MetaPathFinder
  32. from importlib.util import find_spec
  33. class _Finder(MetaPathFinder):
  34. """A meta path finder that allows patching the imported distutils modules"""
  35. fullname = None
  36. # lock[0] is threading.Lock(), but initialized lazily to avoid importing threading very early at startup,
  37. # because there are gevent-based applications that need to be first to import threading by themselves.
  38. # See https://github.com/pypa/virtualenv/issues/1895 for details.
  39. lock = []
  40. def find_spec(self, fullname, path, target=None):
  41. if fullname in _DISTUTILS_PATCH and self.fullname is None:
  42. # initialize lock[0] lazily
  43. if len(self.lock) == 0:
  44. import threading
  45. lock = threading.Lock()
  46. # there is possibility that two threads T1 and T2 are simultaneously running into find_spec,
  47. # observing .lock as empty, and further going into hereby initialization. However due to the GIL,
  48. # list.append() operation is atomic and this way only one of the threads will "win" to put the lock
  49. # - that every thread will use - into .lock[0].
  50. # https://docs.python.org/3/faq/library.html#what-kinds-of-global-value-mutation-are-thread-safe
  51. self.lock.append(lock)
  52. with self.lock[0]:
  53. self.fullname = fullname
  54. try:
  55. spec = find_spec(fullname, path)
  56. if spec is not None:
  57. # https://www.python.org/dev/peps/pep-0451/#how-loading-will-work
  58. is_new_api = hasattr(spec.loader, "exec_module")
  59. func_name = "exec_module" if is_new_api else "load_module"
  60. old = getattr(spec.loader, func_name)
  61. func = self.exec_module if is_new_api else self.load_module
  62. if old is not func:
  63. try:
  64. setattr(spec.loader, func_name, partial(func, old))
  65. except AttributeError:
  66. pass # C-Extension loaders are r/o such as zipimporter with <python 3.7
  67. return spec
  68. finally:
  69. self.fullname = None
  70. @staticmethod
  71. def exec_module(old, module):
  72. old(module)
  73. if module.__name__ in _DISTUTILS_PATCH:
  74. patch_dist(module)
  75. @staticmethod
  76. def load_module(old, name):
  77. module = old(name)
  78. if module.__name__ in _DISTUTILS_PATCH:
  79. patch_dist(module)
  80. return module
  81. sys.meta_path.insert(0, _Finder())
  82. else:
  83. # https://www.python.org/dev/peps/pep-0302/
  84. from imp import find_module
  85. from pkgutil import ImpImporter, ImpLoader
  86. class _VirtualenvImporter(object, ImpImporter):
  87. def __init__(self, path=None):
  88. object.__init__(self)
  89. ImpImporter.__init__(self, path)
  90. def find_module(self, fullname, path=None):
  91. if fullname in _DISTUTILS_PATCH:
  92. try:
  93. return _VirtualenvLoader(fullname, *find_module(fullname.split(".")[-1], path))
  94. except ImportError:
  95. pass
  96. return None
  97. class _VirtualenvLoader(object, ImpLoader):
  98. def __init__(self, fullname, file, filename, etc):
  99. object.__init__(self)
  100. ImpLoader.__init__(self, fullname, file, filename, etc)
  101. def load_module(self, fullname):
  102. module = super(_VirtualenvLoader, self).load_module(fullname)
  103. patch_dist(module)
  104. module.__loader__ = None # distlib fallback
  105. return module
  106. sys.meta_path.append(_VirtualenvImporter())