Skip to content

Commit

Permalink
Move InstallRequirement.from_editable to a constructors module
Browse files Browse the repository at this point in the history
  • Loading branch information
pradyunsg committed Aug 21, 2018
1 parent fc4ce3d commit 69b494a
Show file tree
Hide file tree
Showing 7 changed files with 133 additions and 105 deletions.
3 changes: 2 additions & 1 deletion src/pip/_internal/cli/base_command.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
)
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.req_file import parse_requirements
from pip._internal.req.req_install import InstallRequirement
from pip._internal.utils.logging import setup_logging
Expand Down Expand Up @@ -216,7 +217,7 @@ def populate_requirement_set(requirement_set, args, options, finder,
requirement_set.add_requirement(req_to_add)

for req in options.editables:
req_to_add = InstallRequirement.from_editable(
req_to_add = install_req_from_editable(
req,
isolated=options.isolated_mode,
wheel_cache=wheel_cache
Expand Down
3 changes: 2 additions & 1 deletion src/pip/_internal/operations/freeze.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

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.req_file import COMMENT_RE
from pip._internal.utils.deprecation import deprecated
from pip._internal.utils.misc import (
Expand Down Expand Up @@ -99,7 +100,7 @@ def freeze(
line = line[2:].strip()
else:
line = line[len('--editable'):].strip().lstrip('=')
line_req = InstallRequirement.from_editable(
line_req = install_req_from_editable(
line,
isolated=isolated,
wheel_cache=wheel_cache,
Expand Down
116 changes: 116 additions & 0 deletions src/pip/_internal/req/constructors.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
"""Backing implementation for InstallRequirement's various constructors
The idea here is that these formed a major chunk of InstallRequirement's size
so, moving them and support code dedicated to them outside of that class
helps creates for better understandability for the rest of the code.
These are meant to be used elsewhere within pip to create instances of
InstallRequirement.
"""

import os

from pip._vendor.packaging.requirements import InvalidRequirement, Requirement

# XXX: Temporarily importing _strip_extras
from pip._internal.download import 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.vcs import vcs

__all__ = ["install_req_from_editable", "parse_editable"]


def parse_editable(editable_req):
"""Parses an editable requirement into:
- a requirement name
- an URL
- extras
- editable options
Accepted requirements:
svn+http://blahblah@rev#egg=Foobar[baz]&subdirectory=version_subdir
.[some_extra]
"""

url = editable_req

# If a file path is specified with extras, strip off the extras.
url_no_extras, extras = _strip_extras(url)

if os.path.isdir(url_no_extras):
if not os.path.exists(os.path.join(url_no_extras, 'setup.py')):
raise InstallationError(
"Directory %r is not installable. File 'setup.py' not found." %
url_no_extras
)
# Treating it as code that has already been checked out
url_no_extras = path_to_url(url_no_extras)

if url_no_extras.lower().startswith('file:'):
package_name = Link(url_no_extras).egg_fragment
if extras:
return (
package_name,
url_no_extras,
Requirement("placeholder" + extras.lower()).extras,
)
else:
return package_name, url_no_extras, None

for version_control in vcs:
if url.lower().startswith('%s:' % version_control):
url = '%s+%s' % (version_control, url)
break

if '+' not in url:
raise InstallationError(
'%s should either be a path to a local project or a VCS url '
'beginning with svn+, git+, hg+, or bzr+' %
editable_req
)

vc_type = url.split('+', 1)[0].lower()

if not vcs.get_backend(vc_type):
error_message = 'For --editable=%s only ' % editable_req + \
', '.join([backend.name + '+URL' for backend in vcs.backends]) + \
' is currently supported'
raise InstallationError(error_message)

package_name = Link(url).egg_fragment
if not package_name:
raise InstallationError(
"Could not detect requirement name for '%s', please specify one "
"with #egg=your_package_name" % editable_req
)
return package_name, url, None


def install_req_from_editable(
editable_req, comes_from=None, isolated=False, options=None,
wheel_cache=None, constraint=False
):
name, url, extras_override = parse_editable(editable_req)
if url.startswith('file:'):
source_dir = url_to_path(url)
else:
source_dir = None

if name is not None:
try:
req = Requirement(name)
except InvalidRequirement:
raise InstallationError("Invalid requirement: '%s'" % name)
else:
req = None
return InstallRequirement(
req, comes_from, source_dir=source_dir,
editable=True,
link=Link(url),
constraint=constraint,
isolated=isolated,
options=options if options else {},
wheel_cache=wheel_cache,
extras=extras_override or (),
)
3 changes: 2 additions & 1 deletion src/pip/_internal/req/req_file.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
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

__all__ = ['parse_requirements']
Expand Down Expand Up @@ -159,7 +160,7 @@ def process_line(line, filename, line_number, finder=None, comes_from=None,
# yield an editable requirement
elif opts.editables:
isolated = options.isolated_mode if options else False
yield InstallRequirement.from_editable(
yield install_req_from_editable(
opts.editables[0], comes_from=line_comes_from,
constraint=constraint, isolated=isolated, wheel_cache=wheel_cache
)
Expand Down
96 changes: 1 addition & 95 deletions src/pip/_internal/req/req_install.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,7 @@

from pip._internal import wheel
from pip._internal.build_env import NoOpBuildEnvironment
from pip._internal.download import (
is_archive_file, is_url, path_to_url, url_to_path,
)
from pip._internal.download import is_archive_file, is_url, path_to_url
from pip._internal.exceptions import InstallationError
from pip._internal.locations import (
PIP_DELETE_MARKER_FILENAME, running_under_virtualenv,
Expand Down Expand Up @@ -149,33 +147,6 @@ def __init__(self, req, comes_from, source_dir=None, editable=False,

# Constructors
# TODO: Move these out of this class into custom methods.
@classmethod
def from_editable(cls, editable_req, comes_from=None, isolated=False,
options=None, wheel_cache=None, constraint=False):
name, url, extras_override = parse_editable(editable_req)
if url.startswith('file:'):
source_dir = url_to_path(url)
else:
source_dir = None

if name is not None:
try:
req = Requirement(name)
except InvalidRequirement:
raise InstallationError("Invalid requirement: '%s'" % name)
else:
req = None
return cls(
req, comes_from, source_dir=source_dir,
editable=True,
link=Link(url),
constraint=constraint,
isolated=isolated,
options=options if options else {},
wheel_cache=wheel_cache,
extras=extras_override or (),
)

@classmethod
def from_req(cls, req, comes_from=None, isolated=False, wheel_cache=None):
try:
Expand Down Expand Up @@ -1029,71 +1000,6 @@ def get_install_args(self, global_options, record_filename, root, prefix,
return install_args


def parse_editable(editable_req):
"""Parses an editable requirement into:
- a requirement name
- an URL
- extras
- editable options
Accepted requirements:
svn+http://blahblah@rev#egg=Foobar[baz]&subdirectory=version_subdir
.[some_extra]
"""

url = editable_req

# If a file path is specified with extras, strip off the extras.
url_no_extras, extras = _strip_extras(url)

if os.path.isdir(url_no_extras):
if not os.path.exists(os.path.join(url_no_extras, 'setup.py')):
raise InstallationError(
"Directory %r is not installable. File 'setup.py' not found." %
url_no_extras
)
# Treating it as code that has already been checked out
url_no_extras = path_to_url(url_no_extras)

if url_no_extras.lower().startswith('file:'):
package_name = Link(url_no_extras).egg_fragment
if extras:
return (
package_name,
url_no_extras,
Requirement("placeholder" + extras.lower()).extras,
)
else:
return package_name, url_no_extras, None

for version_control in vcs:
if url.lower().startswith('%s:' % version_control):
url = '%s+%s' % (version_control, url)
break

if '+' not in url:
raise InstallationError(
'%s should either be a path to a local project or a VCS url '
'beginning with svn+, git+, hg+, or bzr+' %
editable_req
)

vc_type = url.split('+', 1)[0].lower()

if not vcs.get_backend(vc_type):
error_message = 'For --editable=%s only ' % editable_req + \
', '.join([backend.name + '+URL' for backend in vcs.backends]) + \
' is currently supported'
raise InstallationError(error_message)

package_name = Link(url).egg_fragment
if not package_name:
raise InstallationError(
"Could not detect requirement name for '%s', please specify one "
"with #egg=your_package_name" % 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.
Expand Down
12 changes: 7 additions & 5 deletions tests/unit/test_req.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,10 @@
from pip._internal.index import PackageFinder
from pip._internal.operations.prepare import RequirementPreparer
from pip._internal.req import InstallRequirement, RequirementSet
from pip._internal.req.constructors import (
install_req_from_editable, parse_editable,
)
from pip._internal.req.req_file import process_line
from pip._internal.req.req_install import parse_editable
from pip._internal.req.req_tracker import RequirementTracker
from pip._internal.resolve import Resolver
from pip._internal.utils.misc import read_text_file
Expand Down Expand Up @@ -85,7 +87,7 @@ def test_environment_marker_extras(self, data):
non-wheel installs.
"""
reqset = RequirementSet()
req = InstallRequirement.from_editable(
req = install_req_from_editable(
data.packages.join("LocalEnvironMarker")
)
req.is_direct = True
Expand Down Expand Up @@ -398,7 +400,7 @@ def test_url_preserved_line_req(self):
def test_url_preserved_editable_req(self):
"""Confirm the url is preserved in a editable requirement"""
url = 'git+http://foo.com@ref#egg=foo'
req = InstallRequirement.from_editable(url)
req = install_req_from_editable(url)
assert req.link.url == url

@pytest.mark.parametrize('path', (
Expand Down Expand Up @@ -512,15 +514,15 @@ def test_extras_for_editable_path_requirement(self):
url = '.[ex1,ex2]'
filename = 'filename'
comes_from = '-r %s (line %s)' % (filename, 1)
req = InstallRequirement.from_editable(url, comes_from=comes_from)
req = install_req_from_editable(url, comes_from=comes_from)
assert len(req.extras) == 2
assert req.extras == {'ex1', 'ex2'}

def test_extras_for_editable_url_requirement(self):
url = 'git+https://url#egg=SomeProject[ex1,ex2]'
filename = 'filename'
comes_from = '-r %s (line %s)' % (filename, 1)
req = InstallRequirement.from_editable(url, comes_from=comes_from)
req = install_req_from_editable(url, comes_from=comes_from)
assert len(req.extras) == 2
assert req.extras == {'ex1', 'ex2'}

Expand Down
5 changes: 3 additions & 2 deletions tests/unit/test_req_file.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
InstallationError, RequirementsFileParseError,
)
from pip._internal.index import PackageFinder
from pip._internal.req.constructors import install_req_from_editable
from pip._internal.req.req_file import (
break_args_options, ignore_comments, join_lines, parse_requirements,
preprocess, process_line, skip_regex,
Expand Down Expand Up @@ -218,15 +219,15 @@ def test_yield_editable_requirement(self):
line = '-e %s' % url
filename = 'filename'
comes_from = '-r %s (line %s)' % (filename, 1)
req = InstallRequirement.from_editable(url, comes_from=comes_from)
req = install_req_from_editable(url, comes_from=comes_from)
assert repr(list(process_line(line, filename, 1))[0]) == repr(req)

def test_yield_editable_constraint(self):
url = 'git+https://url#egg=SomeProject'
line = '-e %s' % url
filename = 'filename'
comes_from = '-c %s (line %s)' % (filename, 1)
req = InstallRequirement.from_editable(
req = install_req_from_editable(
url, comes_from=comes_from, constraint=True)
found_req = list(process_line(line, filename, 1, constraint=True))[0]
assert repr(found_req) == repr(req)
Expand Down

0 comments on commit 69b494a

Please sign in to comment.