Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: Add requirement parsing module. #7020

Closed
wants to merge 9 commits into from
185 changes: 66 additions & 119 deletions src/pip/_internal/req/constructors.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,7 @@

import logging
import os
import re

from pip._vendor.packaging.markers import Marker
from pip._vendor.packaging.requirements import InvalidRequirement, Requirement
from pip._vendor.packaging.specifiers import Specifier
from pip._vendor.pkg_resources import RequirementParseError, parse_requirements
Expand All @@ -24,6 +22,11 @@
from pip._internal.models.index import PyPI, TestPyPI
from pip._internal.models.link import Link
from pip._internal.pyproject import make_pyproject_path
from pip._internal.req.parsing import (
RequirementParsingError,
_strip_extras,
parse_requirement_text,
)
from pip._internal.req.req_install import InstallRequirement
from pip._internal.utils.misc import (
ARCHIVE_EXTENSIONS,
Expand All @@ -33,7 +36,7 @@
)
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
from pip._internal.utils.urls import url_to_path
from pip._internal.vcs import is_url, vcs
from pip._internal.vcs import vcs
from pip._internal.wheel import Wheel

if MYPY_CHECK_RUNNING:
Expand Down Expand Up @@ -61,19 +64,6 @@ def is_archive_file(name):
return False


def _strip_extras(path):
# type: (str) -> Tuple[str, Optional[str]]
m = re.match(r'^(.+)(\[[^\]]+\])$', path)
extras = None
if m:
path_no_extras = m.group(1)
extras = m.group(2)
else:
path_no_extras = path

return path_no_extras, extras


def parse_editable(editable_req):
# type: (str) -> Tuple[Optional[str], str, Optional[Set[str]]]
"""Parses an editable requirement into:
Expand Down Expand Up @@ -156,23 +146,19 @@ def deduce_helpful_msg(req):
:params req: Requirements file path
"""
msg = ""
if os.path.exists(req):
msg = " It does exist."
# Try to parse and check if it is a requirements file.
try:
with open(req, 'r') as fp:
# parse first line only
next(parse_requirements(fp.read()))
msg += " The argument you provided " + \
"(%s) appears to be a" % (req) + \
" requirements file. If that is the" + \
" case, use the '-r' flag to install" + \
" the packages specified within it."
except RequirementParseError:
logger.debug("Cannot parse '%s' as requirements \
file" % (req), exc_info=True)
else:
msg += " File '%s' does not exist." % (req)
# Try to parse and check if it is a requirements file.
try:
with open(req, 'r') as fp:
# parse first line only
next(parse_requirements(fp.read()))
msg += " The argument you provided " + \
"(%s) appears to be a" % (req) + \
" requirements file. If that is the" + \
" case, use the '-r' flag to install" + \
" the packages specified within it."
except RequirementParseError:
logger.debug("Cannot parse '%s' as requirements \
file" % (req), exc_info=True)
return msg


Expand Down Expand Up @@ -232,105 +218,66 @@ def install_req_from_line(
:param line_source: An optional string describing where the line is from,
for logging purposes in case of an error.
"""
if is_url(name):
marker_sep = '; '
else:
marker_sep = ';'
if marker_sep in name:
name, markers_as_string = name.split(marker_sep, 1)
markers_as_string = markers_as_string.strip()
if not markers_as_string:
markers = None
def with_source(text):
if not line_source:
return text
return '{} (from {})'.format(text, line_source)

try:
req = parse_requirement_text(name)
except RequirementParsingError as e:
if e.type_tried not in ['path', 'url'] and (
'=' in name and not any(op in name for op in operators)
):
add_msg = "= is not a valid operator. Did you mean == ?"
else:
markers = Marker(markers_as_string)
else:
markers = None
name = name.strip()
req_as_string = None
path = os.path.normpath(os.path.abspath(name))
link = None
extras_as_string = None

if is_url(name):
link = Link(name)
else:
p, extras_as_string = _strip_extras(path)
looks_like_dir = os.path.isdir(p) and (
os.path.sep in name or
(os.path.altsep is not None and os.path.altsep in name) or
name.startswith('.')
)
if looks_like_dir:
add_msg = "(tried parsing as {})".format(e.type_tried)

msg = with_source('Invalid requirement: {!r}'.format(name))
msg += '\nHint: {}'.format(add_msg)
raise InstallationError(msg)

link = req.link

def get_install_error(text):
return InstallationError(with_source(text))

if link and link.scheme == 'file':
p = link.path
if not os.path.exists(p):
raise get_install_error(
"Requirement '{}' looks like a path, but the "
'file/directory does not exist'.format(name)
)

if os.path.isdir(p):
if not is_installable_dir(p):
raise InstallationError(
raise get_install_error(
"Directory %r is not installable. Neither 'setup.py' "
"nor 'pyproject.toml' found." % name
"nor 'pyproject.toml' found.".format(name)
)
link = Link(path_to_url(p))
elif is_archive_file(p):
if not os.path.isfile(p):
logger.warning(
'Requirement %r looks like a filename, but the '
'file does not exist',
name
)
link = Link(path_to_url(p))

# it's a local file, dir, or url
if link:
# Handle relative file URLs
if link.scheme == 'file' and re.search(r'\.\./', link.url):
link = Link(
path_to_url(os.path.normpath(os.path.abspath(link.path))))
# wheel file
if link.is_wheel:
wheel = Wheel(link.filename) # can raise InvalidWheelFilename
req_as_string = "%s==%s" % (wheel.name, wheel.version)
else:
# set the req to the egg fragment. when it's not there, this
# will become an 'unnamed' requirement
req_as_string = link.egg_fragment

# a requirement specifier
else:
req_as_string = name
elif not is_archive_file(p) and not link.is_wheel:
raise get_install_error(
"Invalid requirement: {!r}, files must be wheels or "
'archives'.format(name) + deduce_helpful_msg(p)
)

if extras_as_string:
extras = Requirement("placeholder" + extras_as_string.lower()).extras
else:
extras = ()
if req_as_string is not None:
# wheel file
if link and link.is_wheel:
wheel = Wheel(link.filename) # can raise InvalidWheelFilename
wheel_req = "%s==%s" % (wheel.name, wheel.version)
try:
req = Requirement(req_as_string)
req.requirement = Requirement(wheel_req)
except InvalidRequirement:
if os.path.sep in req_as_string:
add_msg = "It looks like a path."
add_msg += deduce_helpful_msg(req_as_string)
elif ('=' in req_as_string and
not any(op in req_as_string for op in operators)):
add_msg = "= is not a valid operator. Did you mean == ?"
else:
add_msg = ''
if line_source is None:
source = ''
else:
source = ' (from {})'.format(line_source)
msg = (
'Invalid requirement: {!r}{}'.format(req_as_string, source)
)
if add_msg:
msg += '\nHint: {}'.format(add_msg)
raise InstallationError(msg)
else:
req = None
pass

return InstallRequirement(
req, comes_from, link=link, markers=markers,
req.requirement, comes_from, link=link, markers=req.markers,
use_pep517=use_pep517, isolated=isolated,
options=options if options else {},
wheel_cache=wheel_cache,
constraint=constraint,
extras=extras,
extras=req.extras,
)


Expand Down
Loading