Skip to content

Commit

Permalink
Move InstallRequirement.from_line to constructors module
Browse files Browse the repository at this point in the history
  • Loading branch information
pradyunsg committed Aug 21, 2018
1 parent 69b494a commit a5a07fe
Show file tree
Hide file tree
Showing 11 changed files with 240 additions and 221 deletions.
7 changes: 4 additions & 3 deletions src/pip/_internal/cli/base_command.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,10 @@
)
from pip._internal.index import PackageFinder
from pip._internal.locations import running_under_virtualenv
from pip._internal.req.constructors import install_req_from_editable
from pip._internal.req.constructors import (
install_req_from_editable, install_req_from_line,
)
from pip._internal.req.req_file import parse_requirements
from pip._internal.req.req_install import InstallRequirement
from pip._internal.utils.logging import setup_logging
from pip._internal.utils.misc import get_prog, normalize_path
from pip._internal.utils.outdated import pip_version_check
Expand Down Expand Up @@ -209,7 +210,7 @@ def populate_requirement_set(requirement_set, args, options, finder,
requirement_set.add_requirement(req_to_add)

for req in args:
req_to_add = InstallRequirement.from_line(
req_to_add = install_req_from_line(
req, None, isolated=options.isolated_mode,
wheel_cache=wheel_cache
)
Expand Down
5 changes: 3 additions & 2 deletions src/pip/_internal/commands/uninstall.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@

from pip._internal.cli.base_command import Command
from pip._internal.exceptions import InstallationError
from pip._internal.req import InstallRequirement, parse_requirements
from pip._internal.req import parse_requirements
from pip._internal.req.constructors import install_req_from_line
from pip._internal.utils.misc import protect_pip_from_modification_on_windows


Expand Down Expand Up @@ -47,7 +48,7 @@ def run(self, options, args):
with self._build_session(options) as session:
reqs_to_uninstall = {}
for name in args:
req = InstallRequirement.from_line(
req = install_req_from_line(
name, isolated=options.isolated_mode,
)
if req.name:
Expand Down
7 changes: 4 additions & 3 deletions src/pip/_internal/operations/freeze.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@
from pip._vendor.pkg_resources import RequirementParseError

from pip._internal.exceptions import InstallationError
from pip._internal.req import InstallRequirement
from pip._internal.req.constructors import install_req_from_editable
from pip._internal.req.constructors import (
install_req_from_editable, install_req_from_line,
)
from pip._internal.req.req_file import COMMENT_RE
from pip._internal.utils.deprecation import deprecated
from pip._internal.utils.misc import (
Expand Down Expand Up @@ -106,7 +107,7 @@ def freeze(
wheel_cache=wheel_cache,
)
else:
line_req = InstallRequirement.from_line(
line_req = install_req_from_line(
COMMENT_RE.sub('', line).strip(),
isolated=isolated,
wheel_cache=wheel_cache,
Expand Down
164 changes: 160 additions & 4 deletions src/pip/_internal/req/constructors.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,45 @@
InstallRequirement.
"""

import logging
import os
import re
import traceback

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

# XXX: Temporarily importing _strip_extras
from pip._internal.download import path_to_url, url_to_path
from pip._internal.download import (
is_archive_file, is_url, path_to_url, url_to_path,
)
from pip._internal.exceptions import InstallationError
from pip._internal.models.link import Link
from pip._internal.req.req_install import InstallRequirement, _strip_extras
from pip._internal.req.req_install import InstallRequirement
from pip._internal.utils.misc import is_installable_dir
from pip._internal.vcs import vcs
from pip._internal.wheel import Wheel

__all__ = ["install_req_from_editable", "parse_editable"]
__all__ = [
"install_req_from_editable", "install_req_from_line",
"parse_editable"
]

logger = logging.getLogger(__name__)
operators = Specifier._operators.keys()


def _strip_extras(path):
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):
Expand Down Expand Up @@ -87,6 +114,36 @@ def parse_editable(editable_req):
return package_name, url, None


def deduce_helpful_msg(req):
"""Returns helpful msg in case requirements file does not exist,
or cannot be parsed.
: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=1)
else:
msg += " File '%s' does not exist." % (req)
return msg


# ---- The actual constructors follow ----


def install_req_from_editable(
editable_req, comes_from=None, isolated=False, options=None,
wheel_cache=None, constraint=False
Expand Down Expand Up @@ -114,3 +171,102 @@ def install_req_from_editable(
wheel_cache=wheel_cache,
extras=extras_override or (),
)


def install_req_from_line(
name, comes_from=None, isolated=False, options=None, wheel_cache=None,
constraint=False
):
"""Creates an InstallRequirement from a name, which might be a
requirement, directory containing 'setup.py', filename, or URL.
"""
if is_url(name):
marker_sep = '; '
else:
marker_sep = ';'
if marker_sep in name:
name, markers = name.split(marker_sep, 1)
markers = markers.strip()
if not markers:
markers = None
else:
markers = Marker(markers)
else:
markers = None
name = name.strip()
req = None
path = os.path.normpath(os.path.abspath(name))
link = None
extras = None

if is_url(name):
link = Link(name)
else:
p, extras = _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:
if not is_installable_dir(p):
raise InstallationError(
"Directory %r is not installable. Neither 'setup.py' "
"nor 'pyproject.toml' found." % 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 = "%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 = link.egg_fragment

# a requirement specifier
else:
req = name

if extras:
extras = Requirement("placeholder" + extras.lower()).extras
else:
extras = ()
if req is not None:
try:
req = Requirement(req)
except InvalidRequirement:
if os.path.sep in req:
add_msg = "It looks like a path."
add_msg += deduce_helpful_msg(req)
elif '=' in req and not any(op in req for op in operators):
add_msg = "= is not a valid operator. Did you mean == ?"
else:
add_msg = traceback.format_exc()
raise InstallationError(
"Invalid requirement: '%s'\n%s" % (req, add_msg)
)

return InstallRequirement(
req, comes_from, link=link, markers=markers,
isolated=isolated,
options=options if options else {},
wheel_cache=wheel_cache,
constraint=constraint,
extras=extras,
)
7 changes: 4 additions & 3 deletions src/pip/_internal/req/req_file.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,9 @@
from pip._internal.cli import cmdoptions
from pip._internal.download import get_file_content
from pip._internal.exceptions import RequirementsFileParseError
from pip._internal.req.constructors import install_req_from_editable
from pip._internal.req.req_install import InstallRequirement
from pip._internal.req.constructors import (
install_req_from_editable, install_req_from_line,
)

__all__ = ['parse_requirements']

Expand Down Expand Up @@ -152,7 +153,7 @@ def process_line(line, filename, line_number, finder=None, comes_from=None,
for dest in SUPPORTED_OPTIONS_REQ_DEST:
if dest in opts.__dict__ and opts.__dict__[dest]:
req_options[dest] = opts.__dict__[dest]
yield InstallRequirement.from_line(
yield install_req_from_line(
args_str, line_comes_from, constraint=constraint,
isolated=isolated, options=req_options, wheel_cache=wheel_cache
)
Expand Down
Loading

0 comments on commit a5a07fe

Please sign in to comment.