forked from pantsbuild/pants
-
Notifications
You must be signed in to change notification settings - Fork 0
/
python_requirements.py
101 lines (85 loc) · 3.74 KB
/
python_requirements.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
# Copyright 2014 Pants project contributors (see CONTRIBUTORS.md).
# Licensed under the Apache License, Version 2.0 (see LICENSE).
from pathlib import Path
from typing import Iterable, Mapping, Optional
from pkg_resources import Requirement
from pants.backend.python.target_types import format_invalid_requirement_string_error
from pants.base.build_environment import get_buildroot
class PythonRequirements:
"""Translates a pip requirements file into an equivalent set of `python_requirement_library`
targets.
If the `requirements.txt` file has lines `foo>=3.14` and `bar>=2.7`,
then this will translate to:
python_requirement_library(
name="foo",
requirements=["foo>=3.14"],
)
python_requirement_library(
name="bar",
requirements=["bar>=2.7"],
)
See the requirements file spec here:
https://pip.pypa.io/en/latest/reference/pip_install.html#requirements-file-format
You may also use the parameter `module_mapping` to teach Pants what modules each of your
requirements provide. For any requirement unspecified, Pants will default to the name of the
requirement. This setting is important for Pants to know how to convert your import
statements back into your dependencies. For example:
python_requirements(
module_mapping={
"ansicolors": ["colors"],
"setuptools": ["pkg_resources"],
}
)
"""
def __init__(self, parse_context):
self._parse_context = parse_context
def __call__(
self,
requirements_relpath: str = "requirements.txt",
*,
module_mapping: Optional[Mapping[str, Iterable[str]]] = None,
) -> None:
"""
:param requirements_relpath: The relpath from this BUILD file to the requirements file.
Defaults to a `requirements.txt` file sibling to the BUILD file.
:param module_mapping: a mapping of requirement names to a list of the modules they provide.
For example, `{"ansicolors": ["colors"]}`. Any unspecified requirements will use the
requirement name as the default module, e.g. "Django" will default to
`modules=["django"]`.
"""
req_file = Path(get_buildroot(), self._parse_context.rel_path, requirements_relpath)
requirements = []
for i, line in enumerate(req_file.read_text().splitlines()):
line = line.strip()
if not line or line.startswith("#") or line.startswith("-"):
continue
try:
req = Requirement.parse(line)
except Exception as e:
raise ValueError(
format_invalid_requirement_string_error(
line,
e,
description_of_origin=(
f"{req_file.relative_to(get_buildroot())} at line {i + 1}"
),
)
)
requirements.append(req)
self._parse_context.create_object(
"_python_requirements_file", name=requirements_relpath, sources=[requirements_relpath]
)
requirements_dep = f":{requirements_relpath}"
for parsed_req in requirements:
req_module_mapping = (
{parsed_req.project_name: module_mapping[parsed_req.project_name]}
if module_mapping and parsed_req.project_name in module_mapping
else None
)
self._parse_context.create_object(
"python_requirement_library",
name=parsed_req.project_name,
requirements=[parsed_req],
module_mapping=req_module_mapping,
dependencies=[requirements_dep],
)