Skip to content

Commit ef33103

Browse files
committed
update pyo3, add lint step, add make and pre commit
1 parent 07339ba commit ef33103

13 files changed

Lines changed: 117 additions & 48 deletions

.github/workflows/ci.yml

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,19 @@ permissions:
1111
contents: read
1212

1313
jobs:
14+
lint:
15+
runs-on: ubuntu-latest
16+
steps:
17+
- uses: actions/checkout@v4
18+
- name: Install Rust toolchain
19+
uses: dtolnay/rust-toolchain@stable
20+
with:
21+
components: clippy
22+
- name: Run lint checks
23+
run: make check
24+
1425
test:
26+
needs: lint
1527
runs-on: ${{ matrix.os }}
1628
strategy:
1729
fail-fast: false
@@ -40,9 +52,9 @@ jobs:
4052
- name: Install dependencies
4153
run: poetry install
4254
- name: Build with maturin
43-
run: poetry run maturin develop
55+
run: make build
4456
- name: Run tests
45-
run: poetry run pytest tests/ -v
57+
run: make test
4658

4759
build:
4860
name: Build wheels

.pre-commit-config.yaml

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
repos:
2+
- repo: https://github.com/rust-lang/rust-clippy
3+
rev: v1.77.0
4+
hooks:
5+
- id: clippy
6+
name: clippy
7+
entry: cargo clippy -- -D warnings
8+
language: system
9+
types: [rust]
10+
pass_filenames: false
11+
stages: [commit]
12+
13+
- repo: local
14+
hooks:
15+
- id: cargo-fmt
16+
name: cargo fmt
17+
entry: cargo fmt
18+
language: system
19+
types: [rust]
20+
pass_filenames: false
21+
stages: [commit]
22+
23+
- id: cargo-check
24+
name: cargo check
25+
entry: cargo check
26+
language: system
27+
types: [rust]
28+
pass_filenames: false
29+
stages: [commit]

Cargo.lock

Lines changed: 10 additions & 10 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ name = "ormar_rust_utils"
1010
crate-type = ["cdylib"]
1111

1212
[dependencies]
13-
pyo3 = { version = "0.22", features = ["extension-module"] }
13+
pyo3 = { version = "0.23.5", features = ["extension-module"] }
1414
base64 = "0.22"
1515
serde_json = "1.0"
1616
indexmap = "2.0"

Makefile

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
.PHONY: fmt check test build clean
2+
3+
fmt:
4+
cargo fmt
5+
6+
check:
7+
cargo clippy -- -D warnings
8+
9+
test:
10+
poetry run pytest tests/ -v
11+
12+
build:
13+
poetry run maturin develop
14+
15+
clean:
16+
cargo clean
17+
rm -rf target/
18+
19+
.DEFAULT_GOAL := help
20+
21+
help:
22+
@echo "Available targets:"
23+
@echo " fmt - Format code with rustfmt"
24+
@echo " check - Run clippy linter with strict warnings"
25+
@echo " test - Run tests with pytest"
26+
@echo " build - Build the extension module with maturin"
27+
@echo " clean - Clean build artifacts"

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ python = "^3.10"
4646
[tool.poetry.group.dev.dependencies]
4747
pytest = "^7.0"
4848
maturin = "^1.0"
49+
pre-commit = "^3.0"
4950

5051
[tool.pytest.ini_options]
5152
testpaths = ["tests"]

src/group_related.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,8 @@ fn group_related_inner(py: Python<'_>, list_: &[String]) -> PyResult<PyObject> {
4444
let nested = group_related_inner(py, &children)?;
4545
result_items.push((key, nested));
4646
} else {
47-
let py_list = PyList::new_bound(py, &children);
48-
result_items.push((key, py_list.into()));
47+
let py_list = PyList::new(py, &children)?;
48+
result_items.push((key, py_list.unbind().into()));
4949
}
5050
}
5151

@@ -56,10 +56,10 @@ fn group_related_inner(py: Python<'_>, list_: &[String]) -> PyResult<PyObject> {
5656
a_len.cmp(&b_len).then_with(|| a.0.cmp(&b.0))
5757
});
5858

59-
let dict = PyDict::new_bound(py);
59+
let dict = PyDict::new(py);
6060
for (key, value) in &result_items {
6161
dict.set_item(key, value)?;
6262
}
6363

64-
Ok(dict.into())
64+
Ok(dict.unbind().into())
6565
}

src/hash_item.rs

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -45,11 +45,11 @@ fn hash_item_inner(py: Python<'_>, item: &Bound<'_, PyAny>) -> PyResult<PyObject
4545
} else {
4646
value.clone_ref(py)
4747
};
48-
let pair = PyTuple::new_bound(py, &[key.clone_ref(py), processed_value]);
49-
result.push(pair.into());
48+
let pair = PyTuple::new(py, &[key.clone_ref(py), processed_value])?;
49+
result.push(pair.unbind().into());
5050
}
51-
let tuple = PyTuple::new_bound(py, &result);
52-
Ok(tuple.into())
51+
let tuple = PyTuple::new(py, &result)?;
52+
Ok(tuple.unbind().into())
5353
} else if let Ok(list) = item.downcast::<PyList>() {
5454
let mut result: Vec<PyObject> = Vec::with_capacity(list.len());
5555
for (idx, value) in list.iter().enumerate() {
@@ -59,12 +59,12 @@ fn hash_item_inner(py: Python<'_>, item: &Bound<'_, PyAny>) -> PyResult<PyObject
5959
} else {
6060
value.unbind()
6161
};
62-
let idx_obj = idx.to_object(py);
63-
let pair = PyTuple::new_bound(py, &[idx_obj, processed_value]);
64-
result.push(pair.into());
62+
let idx_obj: PyObject = idx.into_pyobject(py)?.into_any().unbind();
63+
let pair = PyTuple::new(py, &[idx_obj, processed_value])?;
64+
result.push(pair.unbind().into());
6565
}
66-
let tuple = PyTuple::new_bound(py, &result);
67-
Ok(tuple.into())
66+
let tuple = PyTuple::new(py, &result)?;
67+
Ok(tuple.unbind().into())
6868
} else {
6969
Err(pyo3::exceptions::PyTypeError::new_err(
7070
"hash_item expects a dict or list",

src/merge_instances.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,11 @@ pub fn group_by_pk(py: Python<'_>, pks: Vec<PyObject>) -> PyResult<PyObject> {
1313
groups.entry(hash).or_default().push(idx);
1414
}
1515

16-
let result = PyList::empty_bound(py);
16+
let result = PyList::empty(py);
1717
for (_hash, indices) in &groups {
18-
let py_indices = PyList::new_bound(py, indices);
19-
result.append(py_indices)?;
18+
let py_indices = PyList::new(py, indices)?;
19+
result.append(&py_indices)?;
2020
}
2121

22-
Ok(result.into())
22+
Ok(result.unbind().into())
2323
}

src/parsers.rs

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use base64::engine::general_purpose::STANDARD as BASE64_STANDARD;
22
use base64::Engine;
33
use pyo3::prelude::*;
4-
use pyo3::types::{PyBytes, PyString};
4+
use pyo3::types::{PyBytes, PyString, PyDict};
55

66
/// Encode bytes to string representation.
77
/// If represent_as_string is true, uses base64 encoding.
@@ -21,11 +21,11 @@ pub fn encode_bytes(
2121

2222
if represent_as_string {
2323
let encoded = BASE64_STANDARD.encode(bytes_val);
24-
Ok(PyString::new_bound(py, &encoded).into())
24+
Ok(PyString::new(py, &encoded).into())
2525
} else {
2626
let s = std::str::from_utf8(bytes_val)
2727
.map_err(|e| pyo3::exceptions::PyUnicodeDecodeError::new_err(e.to_string()))?;
28-
Ok(PyString::new_bound(py, s).into())
28+
Ok(PyString::new(py, s).into())
2929
}
3030
}
3131

@@ -49,17 +49,17 @@ pub fn decode_bytes(
4949
let decoded = BASE64_STANDARD
5050
.decode(s)
5151
.map_err(|e| pyo3::exceptions::PyValueError::new_err(e.to_string()))?;
52-
Ok(PyBytes::new_bound(py, &decoded).into())
52+
Ok(PyBytes::new(py, &decoded).into())
5353
} else {
54-
Ok(PyBytes::new_bound(py, s.as_bytes()).into())
54+
Ok(PyBytes::new(py, s.as_bytes()).into())
5555
}
5656
}
5757

5858
/// Encode a value to JSON string.
5959
/// Handles datetime objects by calling .isoformat() first.
6060
#[pyfunction]
6161
pub fn encode_json(py: Python<'_>, value: &Bound<'_, PyAny>) -> PyResult<PyObject> {
62-
let datetime_mod = py.import_bound("datetime")?;
62+
let datetime_mod = py.import("datetime")?;
6363
let date_type = datetime_mod.getattr("date")?;
6464
let datetime_type = datetime_mod.getattr("datetime")?;
6565
let time_type = datetime_mod.getattr("time")?;
@@ -84,7 +84,7 @@ pub fn encode_json(py: Python<'_>, value: &Bound<'_, PyAny>) -> PyResult<PyObjec
8484
Ok(parsed) => {
8585
let result = serde_json::to_string(&parsed)
8686
.map_err(|e| pyo3::exceptions::PyValueError::new_err(e.to_string()))?;
87-
return Ok(PyString::new_bound(py, &result).into());
87+
return Ok(PyString::new(py, &result).into());
8888
}
8989
Err(_) => {
9090
// Not valid JSON, return as-is
@@ -95,8 +95,8 @@ pub fn encode_json(py: Python<'_>, value: &Bound<'_, PyAny>) -> PyResult<PyObjec
9595

9696
// For other types, use Python's json.dumps
9797
let json_mod = py
98-
.import_bound("orjson")
99-
.or_else(|_| py.import_bound("json"))?;
98+
.import("orjson")
99+
.or_else(|_| py.import("json"))?;
100100

101101
// Check if using orjson (which is always compact) or standard json
102102
let is_orjson = json_mod.getattr("__name__")?.extract::<String>()? == "orjson";
@@ -106,7 +106,7 @@ pub fn encode_json(py: Python<'_>, value: &Bound<'_, PyAny>) -> PyResult<PyObjec
106106
json_mod.call_method1("dumps", (val_bound,))?
107107
} else {
108108
// Standard json: use separators to match orjson's compact format
109-
let kwargs = PyDict::new_bound(py);
109+
let kwargs = PyDict::new(py);
110110
kwargs.set_item("separators", (",", ":"))?;
111111
json_mod.call_method("dumps", (val_bound,), Some(&kwargs))?
112112
};
@@ -116,7 +116,7 @@ pub fn encode_json(py: Python<'_>, value: &Bound<'_, PyAny>) -> PyResult<PyObjec
116116
let bytes_val: &[u8] = dumped.downcast::<PyBytes>()?.as_bytes();
117117
let s = std::str::from_utf8(bytes_val)
118118
.map_err(|e| pyo3::exceptions::PyUnicodeDecodeError::new_err(e.to_string()))?;
119-
Ok(PyString::new_bound(py, s).into())
119+
Ok(PyString::new(py, s).into())
120120
} else {
121121
Ok(dumped.unbind())
122122
}

0 commit comments

Comments
 (0)