Skip to content

Commit b7dfba6

Browse files
Fix CI failures: add Python setup, fix clippy deprecations, fix stubtest compat
- rust-test.yml, static-checks.yml: add actions/setup-python so PyO3 can link against libpython during cargo test and clippy - src/*.rs: cargo fmt, replace deprecated PyObject with Py<PyAny>, replace downcast() with cast() - tests/test_stubs.py: conditionally use --ignore-disjoint-bases only when the installed mypy version supports it (fixes min-deps matrix)
1 parent e4336e1 commit b7dfba6

8 files changed

Lines changed: 81 additions & 54 deletions

File tree

.github/workflows/rust-test.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,11 @@ jobs:
3434
with:
3535
persist-credentials: false
3636

37+
- name: Set up Python
38+
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
39+
with:
40+
python-version: "3.10"
41+
3742
- name: Install Rust toolchain
3843
uses: dtolnay/rust-toolchain@3c5f7ea28cd621ae0bf5283f0e981fb97b8a7af9 # master
3944
with:

.github/workflows/static-checks.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,11 @@ jobs:
3939
with:
4040
persist-credentials: false
4141

42+
- name: Set up Python
43+
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
44+
with:
45+
python-version: "3.10"
46+
4247
- name: Install Rust toolchain
4348
uses: dtolnay/rust-toolchain@3c5f7ea28cd621ae0bf5283f0e981fb97b8a7af9 # master
4449
with:

src/convert.rs

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -34,22 +34,23 @@ pub fn py_to_yaml_value(obj: &Bound<'_, PyAny>) -> PyResult<Value> {
3434
if f.is_finite() {
3535
Ok(Value::Number(serde_yaml::Number::from(f)))
3636
} else {
37-
Err(PyErr::new::<pyo3::exceptions::PyValueError, _>(
38-
format!("Cannot convert float value {f} to YAML number"),
39-
))
37+
Err(PyErr::new::<pyo3::exceptions::PyValueError, _>(format!(
38+
"Cannot convert float value {f} to YAML number"
39+
)))
4040
}
4141
} else if obj.is_instance_of::<PyString>() {
4242
Ok(Value::String(obj.extract::<String>()?))
4343
} else if obj.is_instance_of::<PyList>() {
44-
let list = obj.downcast::<PyList>()?;
44+
let list = obj.cast::<PyList>()?;
4545
let items: PyResult<Vec<Value>> = list.iter().map(|item| py_to_yaml_value(&item)).collect();
4646
Ok(Value::Sequence(items?))
4747
} else if obj.is_instance_of::<PyTuple>() {
48-
let tuple = obj.downcast::<PyTuple>()?;
49-
let items: PyResult<Vec<Value>> = tuple.iter().map(|item| py_to_yaml_value(&item)).collect();
48+
let tuple = obj.cast::<PyTuple>()?;
49+
let items: PyResult<Vec<Value>> =
50+
tuple.iter().map(|item| py_to_yaml_value(&item)).collect();
5051
Ok(Value::Sequence(items?))
5152
} else if obj.is_instance_of::<PyDict>() {
52-
let dict = obj.downcast::<PyDict>()?;
53+
let dict = obj.cast::<PyDict>()?;
5354
let mut mapping = serde_yaml::Mapping::new();
5455
for (k, v) in dict.iter() {
5556
let key = py_to_yaml_value(&k)?;
@@ -71,7 +72,7 @@ pub fn py_to_yaml_value(obj: &Bound<'_, PyAny>) -> PyResult<Value> {
7172
/// This function recurses without a depth limit. The input comes from
7273
/// `serde_yaml::from_str`, which enforces its own recursion limit (128
7374
/// by default), so the nesting depth is bounded in practice.
74-
pub fn yaml_value_to_py(py: Python<'_>, value: &Value) -> PyResult<PyObject> {
75+
pub fn yaml_value_to_py(py: Python<'_>, value: &Value) -> PyResult<Py<PyAny>> {
7576
match value {
7677
Value::Null => Ok(py.None()),
7778
Value::Bool(b) => Ok(b.into_pyobject(py)?.as_any().to_owned().unbind()),
@@ -152,7 +153,10 @@ mod tests {
152153
// i64::MAX + 1 is a valid u64 but overflows i64
153154
let large = (i64::MAX as u64 + 1).into_pyobject(py).unwrap().into_any();
154155
let val = py_to_yaml_value(&large).unwrap();
155-
assert_eq!(val, Value::Number(serde_yaml::Number::from(i64::MAX as u64 + 1)));
156+
assert_eq!(
157+
val,
158+
Value::Number(serde_yaml::Number::from(i64::MAX as u64 + 1))
159+
);
156160
});
157161
}
158162

@@ -192,10 +196,7 @@ mod tests {
192196
pyo3::prepare_freethreaded_python();
193197
Python::with_gil(|py| {
194198
let s = "hello".into_pyobject(py).unwrap().into_any();
195-
assert_eq!(
196-
py_to_yaml_value(&s).unwrap(),
197-
Value::String("hello".into())
198-
);
199+
assert_eq!(py_to_yaml_value(&s).unwrap(), Value::String("hello".into()));
199200
});
200201
}
201202

src/document.rs

Lines changed: 9 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,7 @@ impl PyDocument {
1414
#[new]
1515
fn new(source: &str) -> PyResult<Self> {
1616
let doc = yamlpath::Document::new(source).map_err(|e| {
17-
PyErr::new::<pyo3::exceptions::PyValueError, _>(format!(
18-
"Failed to parse YAML: {e}"
19-
))
17+
PyErr::new::<pyo3::exceptions::PyValueError, _>(format!("Failed to parse YAML: {e}"))
2018
})?;
2119
Ok(Self { inner: doc })
2220
}
@@ -60,7 +58,8 @@ impl PyDocument {
6058
"Feature location is out of bounds",
6159
));
6260
}
63-
source.get(start..end)
61+
source
62+
.get(start..end)
6463
.map(|s| s.to_string())
6564
.ok_or_else(|| {
6665
PyErr::new::<pyo3::exceptions::PyValueError, _>(
@@ -74,7 +73,7 @@ impl PyDocument {
7473
}
7574

7675
/// Parse the YAML value at a route and return it as a Python object.
77-
fn parse_value(&self, py: Python<'_>, route: &PyRoute) -> PyResult<PyObject> {
76+
fn parse_value(&self, py: Python<'_>, route: &PyRoute) -> PyResult<Py<PyAny>> {
7877
let source = self.inner.source();
7978
let r = route.to_yamlpath_route();
8079

@@ -99,10 +98,7 @@ impl PyDocument {
9998
// Calculate the column offset (in bytes) of the value
10099
// start relative to the beginning of its line, so we can
101100
// dedent continuation lines.
102-
let line_start = source[..span.0]
103-
.rfind('\n')
104-
.map(|nl| nl + 1)
105-
.unwrap_or(0);
101+
let line_start = source[..span.0].rfind('\n').map(|nl| nl + 1).unwrap_or(0);
106102
let col = span.0 - line_start;
107103
if col == 0 {
108104
raw.to_string()
@@ -128,7 +124,7 @@ impl PyDocument {
128124
Err(e) => {
129125
return Err(PyErr::new::<pyo3::exceptions::PyKeyError, _>(format!(
130126
"Query error: {e}"
131-
)))
127+
)));
132128
}
133129
}
134130
};
@@ -151,16 +147,14 @@ impl PyDocument {
151147
})
152148
.collect();
153149

154-
let result =
155-
yamlpatch::apply_yaml_patches(&self.inner, &yaml_patches).map_err(|e| {
156-
PyErr::new::<pyo3::exceptions::PyRuntimeError, _>(format!("Patch failed: {e}"))
157-
})?;
150+
let result = yamlpatch::apply_yaml_patches(&self.inner, &yaml_patches).map_err(|e| {
151+
PyErr::new::<pyo3::exceptions::PyRuntimeError, _>(format!("Patch failed: {e}"))
152+
})?;
158153

159154
Ok(Self { inner: result })
160155
}
161156
}
162157

163-
164158
fn convert_feature(feature: &yamlpath::Feature<'_>) -> PyFeature {
165159
PyFeature {
166160
location: PyLocation {

src/lib.rs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,22 +13,22 @@ use types::{PyComponent, PyFeature, PyFeatureKind, PyLocation, PyRoute};
1313
mod _core {
1414
use super::*;
1515

16-
#[pymodule_export]
17-
use super::PyLocation;
18-
#[pymodule_export]
19-
use super::PyFeatureKind;
2016
#[pymodule_export]
2117
use super::PyComponent;
2218
#[pymodule_export]
23-
use super::PyRoute;
19+
use super::PyDocument;
2420
#[pymodule_export]
2521
use super::PyFeature;
2622
#[pymodule_export]
27-
use super::PyDocument;
23+
use super::PyFeatureKind;
24+
#[pymodule_export]
25+
use super::PyLocation;
2826
#[pymodule_export]
2927
use super::PyOp;
3028
#[pymodule_export]
3129
use super::PyPatch;
30+
#[pymodule_export]
31+
use super::PyRoute;
3232

3333
#[pyfunction]
3434
fn apply_patches(source: &str, patches: Vec<PyPatch>) -> PyResult<String> {

src/ops.rs

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -49,11 +49,11 @@ impl PyOp {
4949
/// Merge key-value pairs into an existing mapping.
5050
#[staticmethod]
5151
fn merge_into(key: &str, updates: &Bound<'_, PyAny>) -> PyResult<Self> {
52-
let dict = updates.downcast::<pyo3::types::PyDict>().map_err(|_| {
53-
let type_name = updates.get_type().name().map_or_else(
54-
|_| "<unknown>".to_string(),
55-
|n| n.to_string(),
56-
);
52+
let dict = updates.cast::<pyo3::types::PyDict>().map_err(|_| {
53+
let type_name = updates
54+
.get_type()
55+
.name()
56+
.map_or_else(|_| "<unknown>".to_string(), |n| n.to_string());
5757
PyErr::new::<pyo3::exceptions::PyTypeError, _>(format!(
5858
"updates must be a dict, got {type_name}"
5959
))
@@ -88,7 +88,11 @@ impl PyOp {
8888
format!("Op.merge_into({}, ...)", yaml_str_repr(key))
8989
}
9090
yamlpatch::Op::RewriteFragment { from, to } => {
91-
format!("Op.rewrite_fragment({}, {})", yaml_str_repr(&format!("{from:?}")), yaml_str_repr(to))
91+
format!(
92+
"Op.rewrite_fragment({}, {})",
93+
yaml_str_repr(&format!("{from:?}")),
94+
yaml_str_repr(to)
95+
)
9296
}
9397
yamlpatch::Op::ReplaceComment { new } => {
9498
format!("Op.replace_comment({})", yaml_str_repr(new))

src/types.rs

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,9 @@ impl PyLocation {
1515
#[new]
1616
fn new(start: usize, end: usize) -> PyResult<Self> {
1717
if start > end {
18-
return Err(PyErr::new::<pyo3::exceptions::PyValueError, _>(
19-
format!("Location start ({start}) must not exceed end ({end})"),
20-
));
18+
return Err(PyErr::new::<pyo3::exceptions::PyValueError, _>(format!(
19+
"Location start ({start}) must not exceed end ({end})"
20+
)));
2121
}
2222
Ok(Self { start, end })
2323
}
@@ -37,7 +37,14 @@ impl From<yamlpath::Location> for PyLocation {
3737
}
3838

3939
/// The kind of a YAML feature.
40-
#[pyclass(name = "FeatureKind", module = "yamltrip._core", frozen, eq, eq_int, hash)]
40+
#[pyclass(
41+
name = "FeatureKind",
42+
module = "yamltrip._core",
43+
frozen,
44+
eq,
45+
eq_int,
46+
hash
47+
)]
4148
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
4249
pub enum PyFeatureKind {
4350
Scalar,
@@ -255,9 +262,7 @@ mod tests {
255262

256263
#[test]
257264
fn test_empty_route_conversion() {
258-
let route = PyRoute {
259-
components: vec![],
260-
};
265+
let route = PyRoute { components: vec![] };
261266
let _yamlpath_route = route.to_yamlpath_route();
262267
}
263268

tests/test_stubs.py

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,17 +6,30 @@
66
import pytest
77

88

9+
def _stubtest_supports_ignore_disjoint_bases() -> bool:
10+
"""Check if the installed mypy stubtest supports --ignore-disjoint-bases."""
11+
result = subprocess.run(
12+
[sys.executable, "-m", "mypy.stubtest", "--help"],
13+
capture_output=True,
14+
text=True,
15+
)
16+
return "--ignore-disjoint-bases" in result.stdout
17+
18+
919
def test_stubtest_core():
1020
"""Run mypy stubtest against yamltrip._core to catch stub drift."""
21+
cmd = [
22+
sys.executable,
23+
"-m",
24+
"mypy.stubtest",
25+
"yamltrip._core",
26+
]
27+
if _stubtest_supports_ignore_disjoint_bases():
28+
cmd.append("--ignore-disjoint-bases")
29+
1130
try:
1231
result = subprocess.run(
13-
[
14-
sys.executable,
15-
"-m",
16-
"mypy.stubtest",
17-
"yamltrip._core",
18-
"--ignore-disjoint-bases",
19-
],
32+
cmd,
2033
capture_output=True,
2134
text=True,
2235
)

0 commit comments

Comments
 (0)