Development of an internal social media platform with personalised dashboards for students
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

constructors.py 9.3KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298
  1. """Backing implementation for InstallRequirement's various constructors
  2. The idea here is that these formed a major chunk of InstallRequirement's size
  3. so, moving them and support code dedicated to them outside of that class
  4. helps creates for better understandability for the rest of the code.
  5. These are meant to be used elsewhere within pip to create instances of
  6. InstallRequirement.
  7. """
  8. import logging
  9. import os
  10. import re
  11. import traceback
  12. from pip._vendor.packaging.markers import Marker
  13. from pip._vendor.packaging.requirements import InvalidRequirement, Requirement
  14. from pip._vendor.packaging.specifiers import Specifier
  15. from pip._vendor.pkg_resources import RequirementParseError, parse_requirements
  16. from pip._internal.download import (
  17. is_archive_file, is_url, path_to_url, url_to_path,
  18. )
  19. from pip._internal.exceptions import InstallationError
  20. from pip._internal.models.index import PyPI, TestPyPI
  21. from pip._internal.models.link import Link
  22. from pip._internal.req.req_install import InstallRequirement
  23. from pip._internal.utils.misc import is_installable_dir
  24. from pip._internal.vcs import vcs
  25. from pip._internal.wheel import Wheel
  26. __all__ = [
  27. "install_req_from_editable", "install_req_from_line",
  28. "parse_editable"
  29. ]
  30. logger = logging.getLogger(__name__)
  31. operators = Specifier._operators.keys()
  32. def _strip_extras(path):
  33. m = re.match(r'^(.+)(\[[^\]]+\])$', path)
  34. extras = None
  35. if m:
  36. path_no_extras = m.group(1)
  37. extras = m.group(2)
  38. else:
  39. path_no_extras = path
  40. return path_no_extras, extras
  41. def parse_editable(editable_req):
  42. """Parses an editable requirement into:
  43. - a requirement name
  44. - an URL
  45. - extras
  46. - editable options
  47. Accepted requirements:
  48. svn+http://blahblah@rev#egg=Foobar[baz]&subdirectory=version_subdir
  49. .[some_extra]
  50. """
  51. url = editable_req
  52. # If a file path is specified with extras, strip off the extras.
  53. url_no_extras, extras = _strip_extras(url)
  54. if os.path.isdir(url_no_extras):
  55. if not os.path.exists(os.path.join(url_no_extras, 'setup.py')):
  56. raise InstallationError(
  57. "Directory %r is not installable. File 'setup.py' not found." %
  58. url_no_extras
  59. )
  60. # Treating it as code that has already been checked out
  61. url_no_extras = path_to_url(url_no_extras)
  62. if url_no_extras.lower().startswith('file:'):
  63. package_name = Link(url_no_extras).egg_fragment
  64. if extras:
  65. return (
  66. package_name,
  67. url_no_extras,
  68. Requirement("placeholder" + extras.lower()).extras,
  69. )
  70. else:
  71. return package_name, url_no_extras, None
  72. for version_control in vcs:
  73. if url.lower().startswith('%s:' % version_control):
  74. url = '%s+%s' % (version_control, url)
  75. break
  76. if '+' not in url:
  77. raise InstallationError(
  78. '%s should either be a path to a local project or a VCS url '
  79. 'beginning with svn+, git+, hg+, or bzr+' %
  80. editable_req
  81. )
  82. vc_type = url.split('+', 1)[0].lower()
  83. if not vcs.get_backend(vc_type):
  84. error_message = 'For --editable=%s only ' % editable_req + \
  85. ', '.join([backend.name + '+URL' for backend in vcs.backends]) + \
  86. ' is currently supported'
  87. raise InstallationError(error_message)
  88. package_name = Link(url).egg_fragment
  89. if not package_name:
  90. raise InstallationError(
  91. "Could not detect requirement name for '%s', please specify one "
  92. "with #egg=your_package_name" % editable_req
  93. )
  94. return package_name, url, None
  95. def deduce_helpful_msg(req):
  96. """Returns helpful msg in case requirements file does not exist,
  97. or cannot be parsed.
  98. :params req: Requirements file path
  99. """
  100. msg = ""
  101. if os.path.exists(req):
  102. msg = " It does exist."
  103. # Try to parse and check if it is a requirements file.
  104. try:
  105. with open(req, 'r') as fp:
  106. # parse first line only
  107. next(parse_requirements(fp.read()))
  108. msg += " The argument you provided " + \
  109. "(%s) appears to be a" % (req) + \
  110. " requirements file. If that is the" + \
  111. " case, use the '-r' flag to install" + \
  112. " the packages specified within it."
  113. except RequirementParseError:
  114. logger.debug("Cannot parse '%s' as requirements \
  115. file" % (req), exc_info=1)
  116. else:
  117. msg += " File '%s' does not exist." % (req)
  118. return msg
  119. # ---- The actual constructors follow ----
  120. def install_req_from_editable(
  121. editable_req, comes_from=None, isolated=False, options=None,
  122. wheel_cache=None, constraint=False
  123. ):
  124. name, url, extras_override = parse_editable(editable_req)
  125. if url.startswith('file:'):
  126. source_dir = url_to_path(url)
  127. else:
  128. source_dir = None
  129. if name is not None:
  130. try:
  131. req = Requirement(name)
  132. except InvalidRequirement:
  133. raise InstallationError("Invalid requirement: '%s'" % name)
  134. else:
  135. req = None
  136. return InstallRequirement(
  137. req, comes_from, source_dir=source_dir,
  138. editable=True,
  139. link=Link(url),
  140. constraint=constraint,
  141. isolated=isolated,
  142. options=options if options else {},
  143. wheel_cache=wheel_cache,
  144. extras=extras_override or (),
  145. )
  146. def install_req_from_line(
  147. name, comes_from=None, isolated=False, options=None, wheel_cache=None,
  148. constraint=False
  149. ):
  150. """Creates an InstallRequirement from a name, which might be a
  151. requirement, directory containing 'setup.py', filename, or URL.
  152. """
  153. if is_url(name):
  154. marker_sep = '; '
  155. else:
  156. marker_sep = ';'
  157. if marker_sep in name:
  158. name, markers = name.split(marker_sep, 1)
  159. markers = markers.strip()
  160. if not markers:
  161. markers = None
  162. else:
  163. markers = Marker(markers)
  164. else:
  165. markers = None
  166. name = name.strip()
  167. req = None
  168. path = os.path.normpath(os.path.abspath(name))
  169. link = None
  170. extras = None
  171. if is_url(name):
  172. link = Link(name)
  173. else:
  174. p, extras = _strip_extras(path)
  175. looks_like_dir = os.path.isdir(p) and (
  176. os.path.sep in name or
  177. (os.path.altsep is not None and os.path.altsep in name) or
  178. name.startswith('.')
  179. )
  180. if looks_like_dir:
  181. if not is_installable_dir(p):
  182. raise InstallationError(
  183. "Directory %r is not installable. Neither 'setup.py' "
  184. "nor 'pyproject.toml' found." % name
  185. )
  186. link = Link(path_to_url(p))
  187. elif is_archive_file(p):
  188. if not os.path.isfile(p):
  189. logger.warning(
  190. 'Requirement %r looks like a filename, but the '
  191. 'file does not exist',
  192. name
  193. )
  194. link = Link(path_to_url(p))
  195. # it's a local file, dir, or url
  196. if link:
  197. # Handle relative file URLs
  198. if link.scheme == 'file' and re.search(r'\.\./', link.url):
  199. link = Link(
  200. path_to_url(os.path.normpath(os.path.abspath(link.path))))
  201. # wheel file
  202. if link.is_wheel:
  203. wheel = Wheel(link.filename) # can raise InvalidWheelFilename
  204. req = "%s==%s" % (wheel.name, wheel.version)
  205. else:
  206. # set the req to the egg fragment. when it's not there, this
  207. # will become an 'unnamed' requirement
  208. req = link.egg_fragment
  209. # a requirement specifier
  210. else:
  211. req = name
  212. if extras:
  213. extras = Requirement("placeholder" + extras.lower()).extras
  214. else:
  215. extras = ()
  216. if req is not None:
  217. try:
  218. req = Requirement(req)
  219. except InvalidRequirement:
  220. if os.path.sep in req:
  221. add_msg = "It looks like a path."
  222. add_msg += deduce_helpful_msg(req)
  223. elif '=' in req and not any(op in req for op in operators):
  224. add_msg = "= is not a valid operator. Did you mean == ?"
  225. else:
  226. add_msg = traceback.format_exc()
  227. raise InstallationError(
  228. "Invalid requirement: '%s'\n%s" % (req, add_msg)
  229. )
  230. return InstallRequirement(
  231. req, comes_from, link=link, markers=markers,
  232. isolated=isolated,
  233. options=options if options else {},
  234. wheel_cache=wheel_cache,
  235. constraint=constraint,
  236. extras=extras,
  237. )
  238. def install_req_from_req(
  239. req, comes_from=None, isolated=False, wheel_cache=None
  240. ):
  241. try:
  242. req = Requirement(req)
  243. except InvalidRequirement:
  244. raise InstallationError("Invalid requirement: '%s'" % req)
  245. domains_not_allowed = [
  246. PyPI.file_storage_domain,
  247. TestPyPI.file_storage_domain,
  248. ]
  249. if req.url and comes_from.link.netloc in domains_not_allowed:
  250. # Explicitly disallow pypi packages that depend on external urls
  251. raise InstallationError(
  252. "Packages installed from PyPI cannot depend on packages "
  253. "which are not also hosted on PyPI.\n"
  254. "%s depends on %s " % (comes_from.name, req)
  255. )
  256. return InstallRequirement(
  257. req, comes_from, isolated=isolated, wheel_cache=wheel_cache
  258. )