Skip to content

Commit d26715a

Browse files
committed
feat(version-scanner): simplify targets list input to accept only YAML files
1 parent 3e2e84e commit d26715a

3 files changed

Lines changed: 68 additions & 60 deletions

File tree

scripts/version_scanner/tests/integration/test_scanner_integration.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,8 @@ def test_integration_scan(tmp_path):
3232
"-v", "3.7",
3333
"-p", data_dir,
3434
"--config", config_path,
35-
"-o", "scanner_report.csv"
35+
"-o", "scanner_report.csv",
36+
"--soft-fail"
3637
]
3738

3839
result = subprocess.run(cmd, cwd=tmp_path, capture_output=True, text=True, check=True)

scripts/version_scanner/tests/unit/test_version_scanner.py

Lines changed: 32 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -783,39 +783,49 @@ def test_format_for_console():
783783
assert "python_requires = " not in log_str # Slim format doesn't print context line
784784

785785

786-
def test_parse_targets_inline_json():
787-
from version_scanner import parse_targets
788-
json_str = '{"python": ["3.7", "3.8"], "protobuf": "4.25.8"}'
789-
targets = parse_targets(json_str)
790-
assert targets == [("python", "3.7"), ("python", "3.8"), ("protobuf", "4.25.8")]
791-
792-
def test_parse_targets_inline_yaml():
793-
from version_scanner import parse_targets
794-
yaml_str = """
786+
def test_parse_targets_file(tmp_path):
787+
from version_scanner import parse_targets_file
788+
yaml_file = tmp_path / "targets.yaml"
789+
yaml_file.write_text("""
795790
python:
796791
- "3.7"
797792
- "3.8"
798793
protobuf: "4.25.8"
799-
"""
800-
targets = parse_targets(yaml_str)
794+
""")
795+
targets = parse_targets_file(str(yaml_file))
801796
assert targets == [("python", "3.7"), ("python", "3.8"), ("protobuf", "4.25.8")]
802797

803-
def test_parse_targets_from_file(tmp_path):
804-
from version_scanner import parse_targets
798+
def test_parse_targets_file_not_found():
799+
from version_scanner import parse_targets_file
800+
with pytest.raises(SystemExit) as excinfo:
801+
parse_targets_file("nonexistent_file.yaml")
802+
assert excinfo.value.code == 1
803+
804+
def test_parse_targets_file_invalid_yaml(tmp_path):
805+
from version_scanner import parse_targets_file
806+
yaml_file = tmp_path / "targets.yaml"
807+
yaml_file.write_text("invalid: {")
808+
with pytest.raises(SystemExit) as excinfo:
809+
parse_targets_file(str(yaml_file))
810+
assert excinfo.value.code == 1
811+
812+
def test_parse_targets_file_invalid_structure(tmp_path):
813+
from version_scanner import parse_targets_file
814+
yaml_file = tmp_path / "targets.yaml"
815+
yaml_file.write_text("- not_a_mapping")
816+
with pytest.raises(SystemExit) as excinfo:
817+
parse_targets_file(str(yaml_file))
818+
assert excinfo.value.code == 1
819+
820+
def test_parse_targets_file_invalid_version_type(tmp_path):
821+
from version_scanner import parse_targets_file
805822
yaml_file = tmp_path / "targets.yaml"
806823
yaml_file.write_text("""
807824
python:
808-
- "3.7"
809-
- "3.8"
810-
protobuf: "4.25.8"
825+
- null
811826
""")
812-
targets = parse_targets(str(yaml_file))
813-
assert targets == [("python", "3.7"), ("python", "3.8"), ("protobuf", "4.25.8")]
814-
815-
def test_parse_targets_invalid_syntax():
816-
from version_scanner import parse_targets
817827
with pytest.raises(SystemExit) as excinfo:
818-
parse_targets('{"invalid"')
828+
parse_targets_file(str(yaml_file))
819829
assert excinfo.value.code == 1
820830

821831
def test_scan_repository_multi_targets(tmp_path):

scripts/version_scanner/version_scanner.py

Lines changed: 34 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -593,46 +593,46 @@ def scan_repository(
593593
return results
594594

595595

596-
def parse_targets(targets_input: str) -> List[Tuple[str, str]]:
596+
def parse_targets_file(file_path: str) -> List[Tuple[str, str]]:
597597
"""
598-
Parses a targets input (file path or inline YAML/JSON string) into a list of (dependency, version) tuples.
598+
Parses a YAML targets file into a list of (dependency, version) tuples.
599599
"""
600-
raw_targets = {}
601-
content = targets_input
602-
603-
# Check if the input is a file path
604-
if os.path.exists(targets_input):
605-
try:
606-
with open(targets_input, 'r', encoding='utf-8') as f:
607-
content = f.read()
608-
except PermissionError:
609-
print(f"Error: Permission denied reading targets file: {targets_input}", file=sys.stderr)
610-
sys.exit(1)
611-
except Exception as e:
612-
print(f"Error reading targets file {targets_input}: {e}", file=sys.stderr)
613-
sys.exit(1)
614-
600+
if not os.path.exists(file_path):
601+
print(f"Error: Targets file not found: {file_path}", file=sys.stderr)
602+
sys.exit(1)
603+
615604
try:
616-
raw_targets = yaml.safe_load(content)
605+
with open(file_path, 'r', encoding='utf-8') as f:
606+
raw_targets = yaml.safe_load(f)
607+
except PermissionError:
608+
print(f"Error: Permission denied reading targets file: {file_path}", file=sys.stderr)
609+
sys.exit(1)
617610
except Exception as e:
618-
print(f"Error parsing targets YAML/JSON content: {e}", file=sys.stderr)
611+
print(f"Error reading or parsing targets file {file_path}: {e}", file=sys.stderr)
619612
sys.exit(1)
620613

621614
if not isinstance(raw_targets, dict):
622-
print("Error: Targets input must resolve to a JSON object or YAML mapping", file=sys.stderr)
615+
print("Error: Targets file content must resolve to a YAML mapping", file=sys.stderr)
623616
sys.exit(1)
624617

625618
targets = []
626619
for dep, versions in raw_targets.items():
627620
if isinstance(versions, list):
628621
for v in versions:
622+
if v is None or isinstance(v, (dict, list)):
623+
print(f"Error: Invalid version '{v}' for dependency '{dep}'", file=sys.stderr)
624+
sys.exit(1)
629625
targets.append((str(dep), str(v)))
630-
else:
626+
elif versions is not None and not isinstance(versions, dict):
631627
targets.append((str(dep), str(versions)))
628+
else:
629+
print(f"Error: Invalid version '{versions}' for dependency '{dep}'", file=sys.stderr)
630+
sys.exit(1)
632631

633632
return targets
634633

635634

635+
636636
def main():
637637
script_dir = os.path.dirname(os.path.abspath(__file__))
638638
default_config = os.path.join(script_dir, "regex_config.yaml")
@@ -652,8 +652,8 @@ def main():
652652
)
653653

654654
parser.add_argument(
655-
"--targets",
656-
help="Path to a YAML/JSON targets file, or an inline YAML/JSON string (e.g. 'python: [3.8, 3.9]')"
655+
"--targets-file",
656+
help="Path to a YAML file containing target dependencies and versions."
657657
)
658658

659659
parser.add_argument(
@@ -721,16 +721,16 @@ def main():
721721

722722
# Validation of required inputs
723723
has_single_target = bool(args.dependency and args.version)
724-
has_targets_list = bool(args.targets)
724+
has_targets_file = bool(args.targets_file)
725725

726-
if not (has_single_target or has_targets_list):
727-
parser.error("Must specify either (-d/--dependency AND -v/--version) OR (--targets)")
728-
if has_single_target and has_targets_list:
729-
parser.error("Cannot specify both single target (-d/-v) and targets list (--targets)")
726+
if not (has_single_target or has_targets_file):
727+
parser.error("Must specify either (-d/--dependency AND -v/--version) OR (--targets-file)")
728+
if has_single_target and has_targets_file:
729+
parser.error("Cannot specify both single target (-d/-v) and targets file (--targets-file)")
730730

731731
targets = []
732-
if has_targets_list:
733-
targets = parse_targets(args.targets)
732+
if has_targets_file:
733+
targets = parse_targets_file(args.targets_file)
734734
else:
735735
targets = [(args.dependency, args.version)]
736736

@@ -749,7 +749,7 @@ def main():
749749
elif args.package_file:
750750
target_packages = read_package_file(args.package_file)
751751

752-
if has_targets_list:
752+
if has_targets_file:
753753
print("Starting scan for multiple targets:")
754754
for dep, ver in targets:
755755
print(f" - {dep}: {ver}")
@@ -786,7 +786,7 @@ def main():
786786
rules,
787787
target_packages,
788788
ignore_dirs,
789-
version_string=(None if has_targets_list else args.version),
789+
version_string=(None if has_targets_file else args.version),
790790
targets=targets
791791
)
792792

@@ -810,11 +810,8 @@ def main():
810810
script_dir = os.path.dirname(os.path.abspath(__file__))
811811
results_dir = os.path.join(script_dir, "results")
812812
os.makedirs(results_dir, exist_ok=True)
813-
if has_targets_list:
814-
if os.path.exists(args.targets):
815-
base_name = os.path.splitext(os.path.basename(args.targets))[0]
816-
else:
817-
base_name = "targets"
813+
if has_targets_file:
814+
base_name = os.path.splitext(os.path.basename(args.targets_file))[0]
818815
output_path = os.path.join(results_dir, f"{base_name}-{timestamp}.csv")
819816
else:
820817
output_path = os.path.join(results_dir, f"{args.dependency}-{args.version}-{timestamp}.csv")

0 commit comments

Comments
 (0)