Skip to content

Commit f1bfa8b

Browse files
authored
Merge pull request #504 from shubhbapna/use-url
allow use of url for req, constraint and graph files
2 parents 6235e8c + 43a9c06 commit f1bfa8b

File tree

13 files changed

+98
-47
lines changed

13 files changed

+98
-47
lines changed

src/fromager/__main__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@
107107
@click.option(
108108
"-c",
109109
"--constraints-file",
110-
type=clickext.ClickPath(),
110+
type=str,
111111
help="location of the constraints file",
112112
)
113113
@click.option(
@@ -142,7 +142,7 @@ def main(
142142
patches_dir: pathlib.Path,
143143
settings_file: pathlib.Path,
144144
settings_dir: pathlib.Path,
145-
constraints_file: pathlib.Path,
145+
constraints_file: str,
146146
cleanup: bool,
147147
variant: str,
148148
jobs: int | None,

src/fromager/commands/bootstrap.py

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,11 @@
11
import logging
2-
import pathlib
32
import typing
43

54
import click
65
from packaging.requirements import Requirement
76

87
from .. import (
98
bootstrapper,
10-
clickext,
119
context,
1210
dependency_graph,
1311
metrics,
@@ -28,7 +26,7 @@
2826

2927
def _get_requirements_from_args(
3028
toplevel: typing.Iterable[str],
31-
req_files: typing.Iterable[pathlib.Path],
29+
req_files: typing.Iterable[str],
3230
) -> list[Requirement]:
3331
parsed_req: list[str] = []
3432
parsed_req.extend(toplevel)
@@ -59,14 +57,14 @@ def _get_requirements_from_args(
5957
"--requirements-file",
6058
"requirements_files",
6159
multiple=True,
62-
type=clickext.ClickPath(),
60+
type=str,
6361
help="pip requirements file",
6462
)
6563
@click.option(
6664
"-p",
6765
"--previous-bootstrap-file",
6866
"previous_bootstrap_file",
69-
type=clickext.ClickPath(),
67+
type=str,
7068
help="graph file produced from a previous bootstrap",
7169
)
7270
@click.option(
@@ -79,8 +77,8 @@ def _get_requirements_from_args(
7977
@click.pass_obj
8078
def bootstrap(
8179
wkctx: context.WorkContext,
82-
requirements_files: list[pathlib.Path],
83-
previous_bootstrap_file: pathlib.Path | None,
80+
requirements_files: list[str],
81+
previous_bootstrap_file: str | None,
8482
cache_wheel_server_url: str | None,
8583
toplevel: list[str],
8684
) -> None:

src/fromager/commands/build.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
metrics,
2626
overrides,
2727
progress,
28+
read,
2829
server,
2930
sources,
3031
wheels,
@@ -137,7 +138,7 @@ def build_sequence(
137138
entries: list[BuildSequenceEntry] = []
138139

139140
logger.info("reading build order from %s", build_order_file)
140-
with open(build_order_file, "r") as f:
141+
with read.open_file_or_url(build_order_file) as f:
141142
for entry in progress.progress(json.load(f)):
142143
dist_name = entry["dist"]
143144
resolved_version = Version(entry["version"])

src/fromager/commands/download_sequence.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
from packaging.requirements import Requirement
99
from packaging.version import Version
1010

11-
from .. import context, progress, sources, wheels
11+
from .. import context, progress, read, sources, wheels
1212

1313
logger = logging.getLogger(__name__)
1414

@@ -57,7 +57,7 @@ def download_sequence(
5757
wheel_servers = [sdist_server_url]
5858

5959
logger.info("reading build order from %s", build_order_file)
60-
with open(build_order_file, "r") as f:
60+
with read.open_file_or_url(build_order_file) as f:
6161
build_order = json.load(f)
6262

6363
def download_one(entry: dict[str, typing.Any]):

src/fromager/commands/graph.py

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -36,12 +36,10 @@ def graph():
3636
)
3737
@click.argument(
3838
"graph-file",
39-
type=clickext.ClickPath(),
39+
type=str,
4040
)
4141
@click.pass_obj
42-
def to_constraints(
43-
wkctx: context.WorkContext, graph_file: pathlib.Path, output: pathlib.Path
44-
):
42+
def to_constraints(wkctx: context.WorkContext, graph_file: str, output: pathlib.Path):
4543
"Convert a graph file to a constraints file."
4644
graph = DependencyGraph.from_file(graph_file)
4745
if output:
@@ -59,10 +57,10 @@ def to_constraints(
5957
)
6058
@click.argument(
6159
"graph-file",
62-
type=clickext.ClickPath(),
60+
type=str,
6361
)
6462
@click.pass_obj
65-
def to_dot(wkctx, graph_file: pathlib.Path, output: pathlib.Path):
63+
def to_dot(wkctx: context.WorkContext, graph_file: str, output: pathlib.Path):
6664
"Convert a graph file to a DOT file suitable to pass to graphviz."
6765
graph = DependencyGraph.from_file(graph_file)
6866
if output:
@@ -114,7 +112,7 @@ def get_node_id(node):
114112
@graph.command()
115113
@click.argument(
116114
"graph-file",
117-
type=clickext.ClickPath(),
115+
type=str,
118116
)
119117
@click.pass_obj
120118
def explain_duplicates(wkctx, graph_file):
@@ -183,13 +181,13 @@ def explain_duplicates(wkctx, graph_file):
183181
)
184182
@click.argument(
185183
"graph-file",
186-
type=clickext.ClickPath(),
184+
type=str,
187185
)
188186
@click.argument("package-name", type=str)
189187
@click.pass_obj
190188
def why(
191189
wkctx: context.WorkContext,
192-
graph_file: pathlib.Path,
190+
graph_file: str,
193191
package_name: str,
194192
version: list[Version],
195193
depth: int,

src/fromager/constraints.py

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -40,14 +40,9 @@ def _parse(content: typing.Iterable[str]) -> Constraints:
4040
return Constraints(constraints)
4141

4242

43-
def load(filename: pathlib.Path | None) -> Constraints:
44-
if not filename:
43+
def load(constraints_file: str | pathlib.Path | None) -> Constraints:
44+
if not constraints_file:
4545
return Constraints({})
46-
filepath = pathlib.Path(filename)
47-
if not filepath.exists():
48-
raise FileNotFoundError(
49-
f"constraints file {filepath.absolute()} does not exist, ignoring"
50-
)
51-
logger.info("loading constraints from %s", filepath.absolute())
52-
parsed_req_file = requirements_file.parse_requirements_file(filename)
46+
logger.info("loading constraints from %s", constraints_file)
47+
parsed_req_file = requirements_file.parse_requirements_file(constraints_file)
5348
return _parse(parsed_req_file)

src/fromager/context.py

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,7 @@
88
from packaging.utils import NormalizedName, canonicalize_name
99
from packaging.version import Version
1010

11-
from . import (
12-
constraints,
13-
dependency_graph,
14-
packagesettings,
15-
)
11+
from . import constraints, dependency_graph, packagesettings, request_session
1612

1713
logger = logging.getLogger(__name__)
1814

@@ -25,7 +21,7 @@ class WorkContext:
2521
def __init__(
2622
self,
2723
active_settings: packagesettings.Settings | None,
28-
constraints_file: pathlib.Path | None,
24+
constraints_file: str | None,
2925
patches_dir: pathlib.Path,
3026
sdists_repo: pathlib.Path,
3127
wheels_repo: pathlib.Path,
@@ -45,12 +41,12 @@ def __init__(
4541
max_jobs=max_jobs,
4642
)
4743
self.settings = active_settings
48-
self.input_constraints_file: pathlib.Path | None
44+
self.input_constraints_uri: str | None
4945
if constraints_file is not None:
50-
self.input_constraints_file = constraints_file.absolute()
46+
self.input_constraints_uri = constraints_file
5147
self.constraints = constraints.load(constraints_file)
5248
else:
53-
self.input_constraints_file = None
49+
self.input_constraints_uri = None
5450
self.constraints = constraints.Constraints({})
5551
self.sdists_repo = pathlib.Path(sdists_repo).absolute()
5652
self.sdists_downloads = self.sdists_repo / "downloads"
@@ -88,9 +84,19 @@ def pip_wheel_server_args(self) -> list[str]:
8884

8985
@property
9086
def pip_constraint_args(self) -> list[str]:
91-
if not self.input_constraints_file:
87+
if not self.input_constraints_uri:
9288
return []
93-
return ["--constraint", os.fspath(self.input_constraints_file)]
89+
90+
if self.input_constraints_uri.startswith(("https://", "http://", "file://")):
91+
path_to_constraints_file = self.work_dir / "input-constraints.txt"
92+
if not path_to_constraints_file.exists():
93+
response = request_session.session.get(self.input_constraints_uri)
94+
path_to_constraints_file.write_text(response.text)
95+
else:
96+
path_to_constraints_file = pathlib.Path(self.input_constraints_uri)
97+
98+
path_to_constraints_file = path_to_constraints_file.absolute()
99+
return ["--constraint", os.fspath(path_to_constraints_file)]
94100

95101
def write_to_graph_to_file(self):
96102
with open(self.work_dir / "graph.json", "w") as f:

src/fromager/dependency_graph.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from packaging.utils import NormalizedName, canonicalize_name
88
from packaging.version import Version
99

10+
from .read import open_file_or_url
1011
from .requirements_file import RequirementType
1112

1213
logger = logging.getLogger(__name__)
@@ -122,9 +123,9 @@ def __init__(self) -> None:
122123
@classmethod
123124
def from_file(
124125
cls,
125-
graph_file: pathlib.Path,
126+
graph_file: pathlib.Path | str,
126127
) -> "DependencyGraph":
127-
with open(graph_file) as f:
128+
with open_file_or_url(graph_file) as f:
128129
# TODO: add JSON validation to ensure it is a parsable graph json
129130
raw_graph = typing.cast(dict[str, dict], json.load(f))
130131
return cls.from_dict(raw_graph)

src/fromager/read.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import io
2+
import pathlib
3+
import typing
4+
from contextlib import contextmanager
5+
from urllib.parse import urlparse
6+
7+
from .request_session import session
8+
9+
10+
@contextmanager
11+
def open_file_or_url(
12+
path_or_url: str | pathlib.Path,
13+
) -> typing.Generator[io.TextIOWrapper, typing.Any, None]:
14+
location = str(path_or_url)
15+
if location.startswith("file://"):
16+
location = urlparse(location).path
17+
18+
if location.startswith(("https://", "http://")):
19+
response = session.get(location)
20+
yield io.StringIO(response.text)
21+
else:
22+
f = open(location, "r")
23+
try:
24+
yield f
25+
finally:
26+
f.close()

src/fromager/requirements_file.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
from packaging import markers
77
from packaging.requirements import Requirement
88

9+
from .read import open_file_or_url
10+
911
logger = logging.getLogger(__name__)
1012

1113

@@ -40,11 +42,11 @@ class SourceType(StrEnum):
4042

4143

4244
def parse_requirements_file(
43-
req_file: pathlib.Path,
45+
req_file: str | pathlib.Path,
4446
) -> typing.Iterable[str]:
4547
logger.debug("reading requirements file %s", req_file)
4648
lines = []
47-
with open(req_file, "r") as f:
49+
with open_file_or_url(req_file) as f:
4850
for line in f:
4951
useful, _, _ = line.partition("#")
5052
useful = useful.strip()

0 commit comments

Comments
 (0)