Skip to content

Commit 1c88a1f

Browse files
committed
Attempt to solve MacOS linter
Signed-off-by: Jorge J. Perez <jjperez@ekumenlabs.com>
1 parent b7bc689 commit 1c88a1f

2 files changed

Lines changed: 69 additions & 82 deletions

File tree

pallet_patcher/solver.py

Lines changed: 14 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
# Copyright 2025 Open Source Robotics Foundation, Inc.
22
# Licensed under the Apache License, Version 2.0
33

4-
from packaging.version import Version
54
from packaging.specifiers import SpecifierSet
5+
from packaging.version import Version
66

77

88
# I heavily relied on AI to help with this one.
@@ -13,27 +13,27 @@ def _parse_rust_specifier(spec_str: str) -> SpecifierSet:
1313
clean_spec = spec_str.strip()
1414

1515
# 1. Handle "Explicit Equals" (=1.2.3 -> ==1.2.3)
16-
if clean_spec.startswith("=") and not clean_spec.startswith("=="):
17-
return SpecifierSet(f"=={clean_spec[1:]}")
16+
if clean_spec.startswith('=') and not clean_spec.startswith('=='):
17+
return SpecifierSet(f'=={clean_spec[1:]}')
1818

1919
# 2. Handle Tilde (~1.2.3) - Minimal update
20-
if clean_spec.startswith("~"):
21-
version_part = clean_spec.lstrip("~")
20+
if clean_spec.startswith('~'):
21+
version_part = clean_spec.lstrip('~')
2222
parts = version_part.split('.')
2323
try:
2424
if len(parts) >= 2:
2525
major, minor = int(parts[0]), int(parts[1])
26-
return SpecifierSet(f">={version_part},<{major}.{minor + 1}.0")
26+
return SpecifierSet(f'>={version_part},<{major}.{minor + 1}.0')
2727
elif len(parts) == 1:
2828
major = int(parts[0])
29-
return SpecifierSet(f">={version_part},<{major + 1}.0.0")
29+
return SpecifierSet(f'>={version_part},<{major + 1}.0.0')
3030
except ValueError:
3131
pass
3232

3333
# 3. Handle Caret (^1.2.3) - Maximal update (Compatible)
3434
# Rust: ^1.2.3 is the same as 1.2.3 (it's the default)
3535
# We strip the caret and let it fall through to the "Bare" logic below.
36-
if clean_spec.startswith("^"):
36+
if clean_spec.startswith('^'):
3737
clean_spec = clean_spec[1:]
3838

3939
# 4. Handle "Bare" / Caret versions
@@ -44,29 +44,29 @@ def _parse_rust_specifier(spec_str: str) -> SpecifierSet:
4444

4545
# Case A: Major > 0 (e.g. ^1.2.3) -> Lock Major
4646
if major > 0:
47-
return SpecifierSet(f">={clean_spec},<{major + 1}.0.0")
47+
return SpecifierSet(f'>={clean_spec},<{major + 1}.0.0')
4848

4949
# Case B: Major is 0
5050
if major == 0:
5151
# Case B.1: Single digit (^0) -> Allow 0.x.x
5252
if len(parts) == 1:
53-
return SpecifierSet(f">={clean_spec},<1.0.0")
53+
return SpecifierSet(f'>={clean_spec},<1.0.0')
5454

5555
minor = int(parts[1])
5656

5757
# Case B.2: Major 0, Minor > 0 (e.g. ^0.2.3) -> Lock Minor
5858
if minor > 0:
59-
return SpecifierSet(f">={clean_spec},<0.{minor + 1}.0")
59+
return SpecifierSet(f'>={clean_spec},<0.{minor + 1}.0')
6060

6161
# Case B.3: Major 0, Minor 0 (e.g. ^0.0.3) -> Lock Patch
6262
# In Rust, 0.0.x changes are always breaking.
6363
elif minor == 0 and len(parts) > 2:
6464
patch = int(parts[2])
65-
return SpecifierSet(f">={clean_spec},<0.0.{patch + 1}")
65+
return SpecifierSet(f'>={clean_spec},<0.0.{patch + 1}')
6666

6767
# Case B.4: ^0.0 (Implies 0.0.x)
6868
elif minor == 0:
69-
return SpecifierSet(f">={clean_spec},<0.1.0")
69+
return SpecifierSet(f'>={clean_spec},<0.1.0')
7070

7171
except ValueError:
7272
pass # Fallback to standard handling if parsing fails
@@ -77,8 +77,7 @@ def _parse_rust_specifier(spec_str: str) -> SpecifierSet:
7777

7878
def solve_dependency(version_specifier, available_versions):
7979
"""
80-
Find if one version available in our versions_dict matches the expected
81-
version_specifier provided.
80+
Find if ver available in versions_dict matches the expected spec provided.
8281
8382
:param version_spec: Specifier for the version we want to match.
8483
:type version_spec: str

test/test_solver.py

Lines changed: 55 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -1,138 +1,128 @@
11
# Copyright 2025 Open Source Robotics Foundation, Inc.
22
# Licensed under the Apache License, Version 2.0
33

4-
import pytest
4+
from packaging.specifiers import InvalidSpecifier
55
from pallet_patcher.solver import _parse_rust_specifier, solve_dependency
6+
import pytest
67

78

89
# Test code generated with LLM help
9-
@pytest.mark.parametrize("input, expected_matches, expected_non_matches", [
10+
@pytest.mark.parametrize('r_input, expected_matches, expected_non_matches', [
1011
# 1. Explicit Equals (=)
11-
("=1.2.3", ["1.2.3"], ["1.2.4", "1.2.2"]),
12+
('=1.2.3', ['1.2.3'], ['1.2.4', '1.2.2']),
1213
1314
# 2. Tilde Requirements (~)
1415
# ~1.2.3 := >=1.2.3, <1.3.0
15-
("~1.2.3", ["1.2.3", "1.2.9"], ["1.3.0", "1.1.0"]),
16+
('~1.2.3', ['1.2.3', '1.2.9'], ['1.3.0', '1.1.0']),
1617
# ~1.2 := >=1.2, <1.3.0
17-
("~1.2", ["1.2.0", "1.2.5"], ["1.3.0", "1.1.0"]),
18+
('~1.2', ['1.2.0', '1.2.5'], ['1.3.0', '1.1.0']),
1819
# ~1 := >=1, <2.0.0
19-
("~1", ["1.0.0", "1.5.0", "1.9.9"], ["2.0.0", "0.9.0"]),
20+
('~1', ['1.0.0', '1.5.0', '1.9.9'], ['2.0.0', '0.9.0']),
2021
2122
# 3. Caret Requirements (^) - Major > 0
2223
# ^1.2.3 := >=1.2.3, <2.0.0
23-
("^1.2.3", ["1.2.3", "1.9.9"], ["2.0.0", "1.2.2"]),
24+
('^1.2.3', ['1.2.3', '1.9.9'], ['2.0.0', '1.2.2']),
2425
# Bare 1.2.3 (same as ^)
25-
("1.2.3", ["1.2.3", "1.5.0", "1.9.9"], ["2.0.0", "1.2.2"]),
26+
('1.2.3', ['1.2.3', '1.5.0', '1.9.9'], ['2.0.0', '1.2.2']),
2627
2728
# 4. Caret Requirements (^) - Major == 0 (The tricky ones)
2829
2930
# Case B.1: ^0 := >=0.0.0, <1.0.0
30-
("^0", ["0.0.0", "0.1.0", "0.9.9"], ["1.0.0"]),
31-
("0", ["0.1.5"], ["1.0.0"]),
31+
('^0', ['0.0.0', '0.1.0', '0.9.9'], ['1.0.0']),
32+
('0', ['0.1.5'], ['1.0.0']),
3233
3334
# Case B.2: ^0.2.3 (Minor > 0) := >=0.2.3, <0.3.0
34-
("^0.2.3", ["0.2.3", "0.2.9"], ["0.3.0", "0.2.2"]),
35-
("0.2.3", ["0.2.3", "0.2.9"], ["0.3.0"]),
35+
('^0.2.3', ['0.2.3', '0.2.9'], ['0.3.0', '0.2.2']),
36+
('0.2.3', ['0.2.3', '0.2.9'], ['0.3.0']),
3637
3738
# Case B.3: ^0.0.3 (Minor == 0, Patch specified) := >=0.0.3, <0.0.4
38-
("^0.0.3", ["0.0.3"], ["0.0.4", "0.0.2", "0.1.0"]),
39-
("0.0.3", ["0.0.3"], ["0.0.4"]),
39+
('^0.0.3', ['0.0.3'], ['0.0.4', '0.0.2', '0.1.0']),
40+
('0.0.3', ['0.0.3'], ['0.0.4']),
4041
4142
# Case B.4: ^0.0 (Minor == 0, Patch missing) := >=0.0, <0.1.0
42-
("^0.0", ["0.0.0", "0.0.5"], ["0.1.0"]),
43-
("0.0", ["0.0.1"], ["0.1.0"]),
43+
('^0.0', ['0.0.0', '0.0.5'], ['0.1.0']),
44+
('0.0', ['0.0.1'], ['0.1.0']),
4445
])
45-
def test_rust_specifier_logic(input, expected_matches, expected_non_matches):
46+
def test_rust_specifier_logic(r_input, expected_matches, expected_non_matches):
4647
"""
47-
Tests that the converted Python SpecifierSet correctly matches
48-
and excludes the versions dictated by Rust SemVer logic.
48+
Tests that the converted Python SpecifierSet correctly matches.
49+
50+
It should also exclude the versions dictated by Rust SemVer logic.
4951
"""
50-
spec_set = _parse_rust_specifier(input)
52+
spec_set = _parse_rust_specifier(r_input)
5153

5254
for version in expected_matches:
5355
assert version in spec_set, \
54-
f"""Rust spec '{input}' should MATCH {version},
56+
f"""Rust spec '{r_input}' should MATCH {version},
5557
but Python spec {spec_set} did not."""
5658

5759
for version in expected_non_matches:
5860
assert version not in spec_set, \
59-
f"""Rust spec '{input}' should NOT match {version},
61+
f"""Rust spec '{r_input}' should NOT match {version},
6062
but Python spec {spec_set} did."""
6163

6264

63-
@pytest.mark.parametrize("input_str, expected_str_repr", [
64-
(">=1.5", ">=1.5"), # Passthrough standard python
65-
("<2.0", "<2.0"), # Passthrough standard python
66-
("==1.2.3", "==1.2.3"), # Passthrough explicit equality
67-
("", ""), # Empty string handling
65+
@pytest.mark.parametrize('input_str, expected_str_repr', [
66+
('>=1.5', '>=1.5'), # Passthrough standard python
67+
('<2.0', '<2.0'), # Passthrough standard python
68+
('==1.2.3', '==1.2.3'), # Passthrough explicit equality
69+
('', ''), # Empty string handling
6870
])
6971
def test_standard_python_fallback(input_str, expected_str_repr):
70-
"""
71-
Tests that standard Python specifiers or other strings
72-
are passed through to SpecifierSet mostly unchanged.
73-
"""
72+
"""Tests that std Python specifiers or other strings are passed through."""
7473
spec = _parse_rust_specifier(input_str)
7574
# Note: SpecifierSet normalization might change string spacing,
7675
# but the logic checks if it parses without crashing.
7776
assert str(spec) == expected_str_repr or not input_str
7877

7978

8079
def test_invalid_version_strings():
81-
"""
82-
Ensure the function handles malformed strings gracefully
83-
(falling back to SpecifierSet wich might raise or handle it).
84-
"""
80+
"""Ensure the function handles malformed strings gracefully."""
8581
# This falls through to "Bare" logic, fails try/except, hits fallback
8682
# SpecifierSet("invalid") is technically valid in packaging but matches
8783
# nothing or raises InvalidSpecifier depending on version.
8884
# Here we just want to ensure your code doesn't crash internally.
89-
try:
90-
_parse_rust_specifier("invalid_string_with_char")
91-
except Exception as e:
92-
# It is acceptable if SpecifierSet raises, but your parsing logic
93-
# shouldn't
94-
assert "Invalid specifier" in str(e) or isinstance(e, ValueError)
85+
# This acts as both the try/except and the assertion
86+
with pytest.raises((ValueError, InvalidSpecifier)):
87+
_parse_rust_specifier('invalid_string_with_char')
9588

9689

97-
@pytest.mark.parametrize("spec, available, expected", [
90+
@pytest.mark.parametrize('spec, available, expected', [
9891
# 1. Basic Caret Priority
9992
# ^1.2.0 allows >=1.2.0, <2.0.0.
10093
# It should pick 1.9.9 (highest), ignore 2.0.0 (high) and 1.1.0 (low).
101-
("^1.2.0", ["1.1.0", "1.2.0", "1.2.5", "1.9.9", "2.0.0"], "1.9.9"),
94+
('^1.2.0', ['1.1.0', '1.2.0', '1.2.5', '1.9.9', '2.0.0'], '1.9.9'),
10295
10396
# 2. Basic Tilde Priority
10497
# ~1.2.0 allows >=1.2.0, <1.3.0.
10598
# Should pick 1.2.9, ignore 1.3.0.
106-
("~1.2.0", ["1.2.0", "1.2.5", "1.2.9", "1.3.0", "1.4.0"], "1.2.9"),
99+
('~1.2.0', ['1.2.0', '1.2.5', '1.2.9', '1.3.0', '1.4.0'], '1.2.9'),
107100
108-
# 3. Rust "0.x.y" Semantics (Major 0 breaking changes)
101+
# 3. Rust '0.x.y' Semantics (Major 0 breaking changes)
109102
# ^0.2.0 allows >=0.2.0, <0.3.0.
110103
# Should ignore 0.3.0 even though it's higher.
111-
("^0.2.0", ["0.1.9", "0.2.0", "0.2.5", "0.3.0"], "0.2.5"),
104+
('^0.2.0', ['0.1.9', '0.2.0', '0.2.5', '0.3.0'], '0.2.5'),
112105
113-
# 4. Rust "0.0.x" Semantics (Patch 0 breaking changes)
106+
# 4. Rust '0.0.x' Semantics (Patch 0 breaking changes)
114107
# ^0.0.3 allows >=0.0.3, <0.0.4.
115108
# Should strictly match 0.0.3.
116-
("^0.0.3", ["0.0.2", "0.0.3", "0.0.4", "0.1.0"], "0.0.3"),
109+
('^0.0.3', ['0.0.2', '0.0.3', '0.0.4', '0.1.0'], '0.0.3'),
117110
118111
# 5. Exact Match
119-
("=1.5.0", ["1.4.0", "1.5.0", "1.6.0"], "1.5.0"),
112+
('=1.5.0', ['1.4.0', '1.5.0', '1.6.0'], '1.5.0'),
120113
121114
# 6. No Match Found
122115
# Range is >=1.0.0, <2.0.0. Only have 0.9 and 2.1.
123-
("^1.0.0", ["0.9.0", "2.1.0", "3.0.0"], None),
116+
('^1.0.0', ['0.9.0', '2.1.0', '3.0.0'], None),
124117
125118
# 7. Empty List
126-
("^1.0.0", [], None),
119+
('^1.0.0', [], None),
127120
128-
# 8. Handling "Bare" versions (Implies ^)
129-
("1.2.0", ["1.2.0", "1.5.0", "2.0.0"], "1.5.0"),
121+
# 8. Handling 'Bare' versions (Implies ^)
122+
('1.2.0', ['1.2.0', '1.5.0', '2.0.0'], '1.5.0'),
130123
])
131124
def test_solve_dependency_logic(spec, available, expected):
132-
"""
133-
Verifies that the solver picks the highest version that satisfies
134-
the Rust-style specifier.
135-
"""
125+
"""Verify that solver picks the highest ver that satisfies the spec."""
136126
result = solve_dependency(spec, available)
137127
assert result == expected, \
138128
f"""For spec '{spec}' and versions {available}, expected '{expected}'
@@ -142,30 +132,28 @@ def test_solve_dependency_logic(spec, available, expected):
142132
def test_solve_dependency_sorting_correctness():
143133
"""
144134
Specifically tests that 1.10.0 is considered greater than 1.2.0.
135+
145136
String sorting would say "1.2" > "1.10", but numeric sorting says 10 > 2.
146137
"""
147-
spec = "^1.0.0"
138+
spec = '^1.0.0'
148139
# If sorting was string-based, it might encounter 1.2.0 first (descending).
149140
# Correct numeric sort: 1.10.0 is highest.
150-
available = ["1.1.0", "1.2.0", "1.9.0", "1.10.0"]
141+
available = ['1.1.0', '1.2.0', '1.9.0', '1.10.0']
151142

152143
result = solve_dependency(spec, available)
153-
assert result == "1.10.0"
144+
assert result == '1.10.0'
154145

155146

156147
def test_solve_dependency_invalid_input_handling():
157-
"""
158-
Test how the function handles non-integer version strings
159-
given the lambda sorting logic provided.
160-
"""
148+
"""Test how the function handles non-integer version strings."""
161149
# NOTE: The provided function uses `int(part)` which will crash on "beta".
162150
# This test documents that behavior. If you want to support prereleases,
163151
# the sort key in the function needs to change.
164-
spec = "^1.0.0"
165-
available = ["1.0.0", "1.1.0-beta"]
152+
spec = '^1.0.0'
153+
available = ['1.0.0', '1.1.0-beta']
166154

167155
with pytest.raises(ValueError) as excinfo:
168156
solve_dependency(spec, available)
169157

170158
# Confirm it failed where we expected (in the sort lambda)
171-
assert "invalid literal for int()" in str(excinfo.value)
159+
assert 'invalid literal for int()' in str(excinfo.value)

0 commit comments

Comments
 (0)