This repository has been archived by the owner on Oct 20, 2020. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 11
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add support for requirement extras (#28)
- Loading branch information
1 parent
f3c53fe
commit a6c756d
Showing
7 changed files
with
108 additions
and
29 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
import re | ||
from typing import Dict, Set, Tuple, Optional | ||
|
||
|
||
def parse_extras(requirements_path: str) -> Dict[str, Set[str]]: | ||
"""Parse over the requirements.txt file to find extras requested. | ||
Args: | ||
requirements_path: The filepath for the requirements.txt file to parse. | ||
Returns: | ||
A dictionary mapping the requirement name to a set of extras requested. | ||
""" | ||
|
||
extras_requested = {} | ||
with open(requirements_path, "r") as requirements: | ||
# Merge all backslash line continuations so we parse each requirement as a single line. | ||
for line in requirements.read().replace("\\\n", "").split("\n"): | ||
requirement, extras = _parse_requirement_for_extra(line) | ||
if requirement and extras: | ||
extras_requested[requirement] = extras | ||
|
||
return extras_requested | ||
|
||
|
||
def _parse_requirement_for_extra( | ||
requirement: str, | ||
) -> Tuple[Optional[str], Optional[Set[str]]]: | ||
"""Given a requirement string, returns the requirement name and set of extras, if extras specified. | ||
Else, returns (None, None) | ||
""" | ||
|
||
# https://www.python.org/dev/peps/pep-0508/#grammar | ||
extras_pattern = re.compile( | ||
r"^\s*([0-9A-Za-z][0-9A-Za-z_.\-]*)\s*\[\s*([0-9A-Za-z][0-9A-Za-z_.\-]*(?:\s*,\s*[0-9A-Za-z][0-9A-Za-z_.\-]*)*)\s*\]" | ||
) | ||
|
||
matches = extras_pattern.match(requirement) | ||
if matches: | ||
return ( | ||
matches.group(1), | ||
{extra.strip() for extra in matches.group(2).split(",")}, | ||
) | ||
|
||
return None, None |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
import unittest | ||
|
||
from extract_wheels.lib import requirements | ||
|
||
|
||
class TestRequirementExtrasParsing(unittest.TestCase): | ||
def test_parses_requirement_for_extra(self) -> None: | ||
cases = [ | ||
("name[foo]", ("name", frozenset(["foo"]))), | ||
("name[ Foo123 ]", ("name", frozenset(["Foo123"]))), | ||
(" name1[ foo ] ", ("name1", frozenset(["foo"]))), | ||
( | ||
"name [fred,bar] @ http://foo.com ; python_version=='2.7'", | ||
("name", frozenset(["fred", "bar"])), | ||
), | ||
( | ||
"name[quux, strange];python_version<'2.7' and platform_version=='2'", | ||
("name", frozenset(["quux", "strange"])), | ||
), | ||
("name; (os_name=='a' or os_name=='b') and os_name=='c'", (None, None),), | ||
("name@http://foo.com", (None, None),), | ||
] | ||
|
||
for case, expected in cases: | ||
with self.subTest(): | ||
self.assertTupleEqual( | ||
requirements._parse_requirement_for_extra(case), expected | ||
) | ||
|
||
|
||
if __name__ == "__main__": | ||
unittest.main() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters