Refactor to reduce coupling.

* Make ParsedLine record the type of line
* Split handle_line to allow passing arguments only where needed
* Remove unneeded attributes from ParsedRequirement
This commit is contained in:
Paul Moore 2020-02-13 16:16:32 +00:00
parent f2e49b3946
commit e2a57fd1e6
2 changed files with 160 additions and 109 deletions

View File

@ -87,49 +87,46 @@ SUPPORTED_OPTIONS_REQ_DEST = [str(o().dest) for o in SUPPORTED_OPTIONS_REQ]
class ParsedRequirement(object): class ParsedRequirement(object):
def __init__( def __init__(
self, self,
requirement, # type:str
is_editable, # type: bool is_editable, # type: bool
comes_from, # type: str comes_from, # type: str
use_pep517, # type: Optional[bool]
isolated, # type: bool
wheel_cache, # type: Optional[WheelCache]
constraint, # type: bool constraint, # type: bool
args=None, # type: Optional[str]
editables=None, # type: Optional[str]
options=None, # type: Optional[Dict[str, Any]] options=None, # type: Optional[Dict[str, Any]]
line_source=None, # type: Optional[str] line_source=None, # type: Optional[str]
): ):
# type: (...) -> None # type: (...) -> None
self.args = args self.requirement = requirement
self.editables = editables
self.is_editable = is_editable self.is_editable = is_editable
self.comes_from = comes_from self.comes_from = comes_from
self.use_pep517 = use_pep517
self.isolated = isolated
self.options = options self.options = options
self.wheel_cache = wheel_cache
self.constraint = constraint self.constraint = constraint
self.line_source = line_source self.line_source = line_source
def make_requirement(self): def make_requirement(
self,
isolated=False, # type: bool
wheel_cache=None, # type: Optional[WheelCache]
use_pep517=None # type: Optional[bool]
):
# type: (...) -> InstallRequirement # type: (...) -> InstallRequirement
if self.is_editable: if self.is_editable:
req = install_req_from_editable( req = install_req_from_editable(
self.editables, self.requirement,
comes_from=self.comes_from, comes_from=self.comes_from,
use_pep517=self.use_pep517, use_pep517=use_pep517,
constraint=self.constraint, constraint=self.constraint,
isolated=self.isolated, isolated=isolated,
wheel_cache=self.wheel_cache wheel_cache=wheel_cache
) )
else: else:
req = install_req_from_line( req = install_req_from_line(
self.args, self.requirement,
comes_from=self.comes_from, comes_from=self.comes_from,
use_pep517=self.use_pep517, use_pep517=use_pep517,
isolated=self.isolated, isolated=isolated,
options=self.options, options=self.options,
wheel_cache=self.wheel_cache, wheel_cache=wheel_cache,
constraint=self.constraint, constraint=self.constraint,
line_source=self.line_source, line_source=self.line_source,
) )
@ -150,10 +147,21 @@ class ParsedLine(object):
self.filename = filename self.filename = filename
self.lineno = lineno self.lineno = lineno
self.comes_from = comes_from self.comes_from = comes_from
self.args = args
self.opts = opts self.opts = opts
self.constraint = constraint self.constraint = constraint
if args:
self.is_requirement = True
self.is_editable = False
self.requirement = args
elif opts.editables:
self.is_requirement = True
self.is_editable = True
# We don't support multiple -e on one line
self.requirement = opts.editables[0]
else:
self.is_requirement = False
def parse_requirements( def parse_requirements(
filename, # type: str filename, # type: str
@ -188,10 +196,18 @@ def parse_requirements(
for parsed_line in parser.parse(filename, constraint): for parsed_line in parser.parse(filename, constraint):
parsed_req = handle_line( parsed_req = handle_line(
parsed_line, finder, options, session, wheel_cache, use_pep517 parsed_line,
options=options,
finder=finder,
session=session
) )
if parsed_req is not None: if parsed_req is not None:
yield parsed_req.make_requirement() isolated = options.isolated_mode if options else False
yield parsed_req.make_requirement(
isolated,
wheel_cache,
use_pep517
)
def preprocess(content, skip_requirements_regex): def preprocess(content, skip_requirements_regex):
@ -210,18 +226,118 @@ def preprocess(content, skip_requirements_regex):
return lines_enum return lines_enum
def handle_line( def handle_requirement_line(
line, # type: ParsedLine line, # type: ParsedLine
options=None, # type: Optional[optparse.Values]
):
# type: (...) -> ParsedRequirement
# preserve for the nested code path
line_comes_from = '{} {} (line {})'.format(
'-c' if line.constraint else '-r', line.filename, line.lineno,
)
assert line.is_requirement
if line.is_editable:
# For editable requirements, we don't support per-requirement
# options, so just return the parsed requirement.
return ParsedRequirement(
requirement=line.requirement,
is_editable=line.is_editable,
comes_from=line_comes_from,
constraint=line.constraint,
)
else:
if options:
# Disable wheels if the user has specified build options
cmdoptions.check_install_build_global(options, line.opts)
# get the options that apply to requirements
req_options = {}
for dest in SUPPORTED_OPTIONS_REQ_DEST:
if dest in line.opts.__dict__ and line.opts.__dict__[dest]:
req_options[dest] = line.opts.__dict__[dest]
line_source = 'line {} of {}'.format(line.lineno, line.filename)
return ParsedRequirement(
requirement=line.requirement,
is_editable=line.is_editable,
comes_from=line_comes_from,
constraint=line.constraint,
options=req_options,
line_source=line_source,
)
def handle_option_line(
opts, # type: Values
filename, # type: str
lineno, # type: int
finder=None, # type: Optional[PackageFinder] finder=None, # type: Optional[PackageFinder]
options=None, # type: Optional[optparse.Values] options=None, # type: Optional[optparse.Values]
session=None, # type: Optional[PipSession] session=None, # type: Optional[PipSession]
wheel_cache=None, # type: Optional[WheelCache] ):
use_pep517=None, # type: Optional[bool] # type: (...) -> None
# percolate hash-checking option upward
if opts.require_hashes:
options.require_hashes = opts.require_hashes
# set finder options
elif finder:
find_links = finder.find_links
index_urls = finder.index_urls
if opts.index_url:
index_urls = [opts.index_url]
if opts.no_index is True:
index_urls = []
if opts.extra_index_urls:
index_urls.extend(opts.extra_index_urls)
if opts.find_links:
# FIXME: it would be nice to keep track of the source
# of the find_links: support a find-links local path
# relative to a requirements file.
value = opts.find_links[0]
req_dir = os.path.dirname(os.path.abspath(filename))
relative_to_reqs_file = os.path.join(req_dir, value)
if os.path.exists(relative_to_reqs_file):
value = relative_to_reqs_file
find_links.append(value)
search_scope = SearchScope(
find_links=find_links,
index_urls=index_urls,
)
finder.search_scope = search_scope
if opts.pre:
finder.set_allow_all_prereleases()
if session:
for host in opts.trusted_hosts or []:
source = 'line {} of {}'.format(lineno, filename)
session.add_trusted_host(host, source=source)
def handle_line(
line, # type: ParsedLine
options=None, # type: Optional[optparse.Values]
finder=None, # type: Optional[PackageFinder]
session=None, # type: Optional[PipSession]
): ):
# type: (...) -> Optional[ParsedRequirement] # type: (...) -> Optional[ParsedRequirement]
"""Handle a single parsed requirements line; This can result in """Handle a single parsed requirements line; This can result in
creating/yielding requirements, or updating the finder. creating/yielding requirements, or updating the finder.
:param line: The parsed line to be processed.
:param options: CLI options.
:param finder: The finder - updated by non-requirement lines.
:param session: The session - updated by non-requirement lines.
Returns a ParsedRequirement object if the line is a requirement line,
otherwise returns None.
For lines that contain requirements, the only options that have an effect For lines that contain requirements, the only options that have an effect
are from SUPPORTED_OPTIONS_REQ, and they are scoped to the are from SUPPORTED_OPTIONS_REQ, and they are scoped to the
requirement. Other options from SUPPORTED_OPTIONS may be present, but are requirement. Other options from SUPPORTED_OPTIONS may be present, but are
@ -234,87 +350,19 @@ def handle_line(
affect the finder. affect the finder.
""" """
# preserve for the nested code path if parsed_line.is_requirement:
line_comes_from = '{} {} (line {})'.format( parsed_req = handle_requirement_line(parsed_line, options)
'-c' if line.constraint else '-r', line.filename, line.lineno, return parsed_req
) else:
handle_option_line(
# return a line requirement parsed_line.opts,
if line.args: parsed_line.filename,
isolated = options.isolated_mode if options else False parsed_line.lineno,
if options: finder,
cmdoptions.check_install_build_global(options, line.opts) options,
# get the options that apply to requirements session,
req_options = {}
for dest in SUPPORTED_OPTIONS_REQ_DEST:
if dest in line.opts.__dict__ and line.opts.__dict__[dest]:
req_options[dest] = line.opts.__dict__[dest]
line_source = 'line {} of {}'.format(line.lineno, line.filename)
return ParsedRequirement(
args=line.args,
is_editable=False,
comes_from=line_comes_from,
use_pep517=use_pep517,
isolated=isolated,
options=req_options,
wheel_cache=wheel_cache,
constraint=line.constraint,
line_source=line_source,
) )
return None
# return an editable requirement
elif line.opts.editables:
isolated = options.isolated_mode if options else False
return ParsedRequirement(
editables=line.opts.editables[0],
is_editable=True,
comes_from=line_comes_from,
use_pep517=use_pep517,
constraint=line.constraint,
isolated=isolated,
wheel_cache=wheel_cache
)
# percolate hash-checking option upward
elif line.opts.require_hashes:
options.require_hashes = line.opts.require_hashes
# set finder options
elif finder:
find_links = finder.find_links
index_urls = finder.index_urls
if line.opts.index_url:
index_urls = [line.opts.index_url]
if line.opts.no_index is True:
index_urls = []
if line.opts.extra_index_urls:
index_urls.extend(line.opts.extra_index_urls)
if line.opts.find_links:
# FIXME: it would be nice to keep track of the source
# of the find_links: support a find-links local path
# relative to a requirements file.
value = line.opts.find_links[0]
req_dir = os.path.dirname(os.path.abspath(line.filename))
relative_to_reqs_file = os.path.join(req_dir, value)
if os.path.exists(relative_to_reqs_file):
value = relative_to_reqs_file
find_links.append(value)
search_scope = SearchScope(
find_links=find_links,
index_urls=index_urls,
)
finder.search_scope = search_scope
if line.opts.pre:
finder.set_allow_all_prereleases()
if session:
for host in line.opts.trusted_hosts or []:
source = 'line {} of {}'.format(line.lineno, line.filename)
session.add_trusted_host(host, source=source)
return None
class RequirementsFileParser(object): class RequirementsFileParser(object):
@ -342,8 +390,7 @@ class RequirementsFileParser(object):
# type: (str, bool) -> Iterator[ParsedLine] # type: (str, bool) -> Iterator[ParsedLine]
for line in self._parse_file(filename, constraint): for line in self._parse_file(filename, constraint):
if ( if (
not line.args and not line.is_requirement and
not line.opts.editables and
(line.opts.requirements or line.opts.constraints) (line.opts.requirements or line.opts.constraints)
): ):
# parse a nested requirements file # parse a nested requirements file

View File

@ -31,7 +31,11 @@ from pip._internal.req.constructors import (
install_req_from_req_string, install_req_from_req_string,
parse_editable, parse_editable,
) )
from pip._internal.req.req_file import ParsedLine, get_line_parser, handle_line from pip._internal.req.req_file import (
ParsedLine,
get_line_parser,
handle_requirement_line,
)
from pip._internal.req.req_tracker import get_requirement_tracker from pip._internal.req.req_tracker import get_requirement_tracker
from pip._internal.utils.urls import path_to_url from pip._internal.utils.urls import path_to_url
from tests.lib import assert_raises_regexp, make_test_finder, requirements_file from tests.lib import assert_raises_regexp, make_test_finder, requirements_file
@ -48,7 +52,7 @@ def get_processed_req_from_line(line, fname='file', lineno=1):
opts, opts,
False, False,
) )
parsed_req = handle_line(parsed_line) parsed_req = handle_requirement_line(parsed_line)
assert parsed_req is not None assert parsed_req is not None
req = parsed_req.make_requirement() req = parsed_req.make_requirement()
req.is_direct = True req.is_direct = True