Skip to content

Commit 71054e6

Browse files
committed
draft scripts/lockfiles_to_reqs.py
1 parent a4f1221 commit 71054e6

File tree

1 file changed

+112
-0
lines changed

1 file changed

+112
-0
lines changed

scripts/lockfiles_to_reqs.py

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
#!/usr/bin/env python
2+
# Copyright 2025 The StackStorm Authors.
3+
#
4+
# Licensed under the Apache License, Version 2.0 (the "License");
5+
# you may not use this file except in compliance with the License.
6+
# You may obtain a copy of the License at
7+
#
8+
# http://www.apache.org/licenses/LICENSE-2.0
9+
#
10+
# Unless required by applicable law or agreed to in writing, software
11+
# distributed under the License is distributed on an "AS IS" BASIS,
12+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
# See the License for the specific language governing permissions and
14+
# limitations under the License.
15+
16+
import copy
17+
import json
18+
from pathlib import Path
19+
20+
from fixate_requirements import load_fixed_requirements, parse_req_from_line
21+
22+
23+
FIXED_REQUIREMENTS = "fixed-requirements.txt"
24+
TEST_REQUIREMENTS = "test-requirements.txt"
25+
26+
_LOCKFILE = "lockfiles/{resolve}.lock"
27+
TOOL_RESOLVES = ("st2", "bandit", "flake8", "pylint", "black")
28+
# irrelevant resolves: "pants-plugins", "twine"
29+
LOCKFILES = tuple(_LOCKFILE.format(tool) for tool in TOOL_RESOLVES)
30+
31+
32+
def strip_comments_from_pex_json_lockfile(lockfile_bytes: bytes) -> bytes:
33+
"""
34+
Copied from code by Pants Project Contributors (Apache 2.0 licensed):
35+
https://github.com/pantsbuild/pants/blob/release_2.25.0/src/python/pants/backend/python/util_rules/pex_requirements.py#L119-L127
36+
37+
TODO: delete this once we getrid of the legacy fixate requirements files.
38+
"""
39+
return b"\n".join( line for line in lockfile_bytes.splitlines() if not line.lstrip().startswith(b"//") )
40+
41+
42+
def _update(old_req, name, version):
43+
parsedreq = parse_req_from_line(req.requirement, req.line_source)
44+
assert parsedreq.requirement.name == name
45+
specs = tuple(parsedreq.requirement.specifier)
46+
if len(specs) != 1:
47+
return False
48+
spec = specs[0]
49+
if spec.operator == '==' and spec.version != version:
50+
# only change pins; ignore any version range
51+
new_spec = spec.__class__(f"=={version}", spec.prereleases or None)
52+
new_specs = specs.__class__([new_spec], specs.prereleases or None)
53+
new_req = copy.deepcopy(parsedreq.requirement)
54+
new_req.specifier = new_specs
55+
# = dataclasses.replace(parsedreq, requirement=new_req)
56+
57+
return str(new_req)
58+
return False
59+
60+
61+
def plan_update(old_reqs, name, version, reqs_updates):
62+
if name in old_reqs:
63+
old_req = old_reqs[name]
64+
updated_line = _update(old_req, name, version)
65+
if updated_line is not None:
66+
reqs_updates[name] = updated_line
67+
68+
69+
def do_updates(path, reqs_updates):
70+
lines = path.read_text().splitlines()
71+
for name, updated_line in reqs_updates.items():
72+
line_source = fixed_reqs[name].line_source
73+
# line_source fmt is "line <number> of <file_path>"
74+
_, line_number, _ = line_source.split(maxsplits=2)
75+
line_index = line_number - 1
76+
lines[line_index] = updated_line
77+
path.write_text("\n".join(lines) + "\n")
78+
79+
80+
def main():
81+
fixed_path = Path(FIXED_REQUIREMENTS).resolve()
82+
test_path = Path(TEST_REQUIREMENTS).resolve()
83+
fixed_reqs = load_fxed_requirements(FIXED_REQUIREMENTS)
84+
test_reqs = load_fxed_requirements(TEST_REQUIREMENTS)
85+
86+
fixed_reqs_updates = {}
87+
test_reqs_updates = {}
88+
89+
handled = []
90+
for lockfile in LOCKFILES:
91+
lockfile_bytes = strip_comments_from_pex_json_lockfile(
92+
Path(lockfile).read_bytes()
93+
)
94+
pex_lock = json.loads(lockfile_bytes.decode("utf-8"))
95+
locked_requirements = pex_lock["locked_resolves"][0]["locked_requirements"]
96+
locked_reqs_name_version_map = {
97+
req["project_name"]: req["version"]
98+
for req in locked_requirements
99+
}
100+
for name, version in locked_reqs_name_version_map.items():
101+
if name in handled:
102+
continue
103+
plan_update(fixed_reqs, name, version, fixed_reqs_updates)
104+
plan_update(test_reqs, name, version, test_reqs_updates)
105+
handled.append(name)
106+
107+
do_updates(fixed_path, fixed_reqs_updates)
108+
do_updates(test_path, test_reqs_updates)
109+
110+
111+
if __name__ == "__main__":
112+
main()

0 commit comments

Comments
 (0)