Skip to content

Commit f514d6b

Browse files
committed
add type stubs
1 parent a3b2a64 commit f514d6b

7 files changed

Lines changed: 237 additions & 36 deletions

File tree

pyproject.toml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,13 +37,19 @@ classifiers = [
3737
"Tracker" = "https://github.com/MarshalX/python-libipld/issues"
3838
"Author" = "https://github.com/MarshalX"
3939

40+
[dependency-groups]
41+
dev = ["maturin", "pytest"]
42+
4043
[tool.pytest.ini_options]
4144
addopts = [
4245
'--benchmark-columns', 'min,mean,stddev,outliers,rounds,iterations',
4346
'--benchmark-disable', # use --benchmark-enable
4447
]
4548

4649
[tool.maturin]
50+
python-source = "python"
51+
module-name = "libipld._libipld"
52+
bindings = "pyo3"
4753
features = ["pyo3/extension-module"]
4854

4955
[build-system]

pytests/test_cid.py

Lines changed: 22 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,31 +2,37 @@
22

33

44
def test_cid_decode_multibase() -> None:
5-
cid = libipld.decode_cid('bafyreig7jbijxpn4lfhvnvyuwf5u5jyhd7begxwyiqe7ingwxycjdqjjoa')
6-
assert 1 == cid['version']
7-
assert 113 == cid['codec']
8-
assert 18 == cid['hash']['code']
9-
assert 32 == cid['hash']['size']
10-
assert cid['hash']['size'] == len(cid['hash']['digest'])
5+
cid = libipld.decode_cid(
6+
"bafyreig7jbijxpn4lfhvnvyuwf5u5jyhd7begxwyiqe7ingwxycjdqjjoa"
7+
)
8+
assert 1 == cid["version"]
9+
assert 113 == cid["codec"]
10+
assert 18 == cid["hash"]["code"]
11+
assert 32 == cid["hash"]["size"]
12+
assert cid["hash"]["size"] == len(cid["hash"]["digest"])
1113

1214

1315
def test_cid_decode_raw() -> None:
14-
cid = libipld.decode_cid(b'\x01q\x12 \xb6\x81\x1a\x1d\x7f\x8c\x17\x91\xdam\x1bO\x13m\xc0\xe2&y\xea\xfe\xaaX\xd6M~/\xaa\xd5\x89\x0e\x9d\x9c')
15-
assert 1 == cid['version']
16-
assert 113 == cid['codec']
17-
assert 18 == cid['hash']['code']
18-
assert 32 == cid['hash']['size']
19-
assert cid['hash']['size'] == len(cid['hash']['digest'])
16+
cid = libipld.decode_cid(
17+
b"\x01q\x12 \xb6\x81\x1a\x1d\x7f\x8c\x17\x91\xdam\x1bO\x13m\xc0\xe2&y\xea\xfe\xaaX\xd6M~/\xaa\xd5\x89\x0e\x9d\x9c"
18+
)
19+
assert 1 == cid["version"]
20+
assert 113 == cid["codec"]
21+
assert 18 == cid["hash"]["code"]
22+
assert 32 == cid["hash"]["size"]
23+
assert cid["hash"]["size"] == len(cid["hash"]["digest"])
2024

2125

2226
def test_cid_encode_multibase() -> None:
23-
cid = 'bafyreig7jbijxpn4lfhvnvyuwf5u5jyhd7begxwyiqe7ingwxycjdqjjoa'
27+
cid = "bafyreig7jbijxpn4lfhvnvyuwf5u5jyhd7begxwyiqe7ingwxycjdqjjoa"
2428
assert cid == libipld.encode_cid(cid) # because it's already encoded
2529

2630

2731
def test_cid_encode_raw() -> None:
28-
raw_cid = b'\x01q\x12 \xb6\x81\x1a\x1d\x7f\x8c\x17\x91\xdam\x1bO\x13m\xc0\xe2&y\xea\xfe\xaaX\xd6M~/\xaa\xd5\x89\x0e\x9d\x9c'
29-
expected_cid_multibase = 'bafyreifwqenb274mc6i5u3i3j4jw3qhcez46v7vkldle27rpvlkysdu5tq'
32+
raw_cid = b"\x01q\x12 \xb6\x81\x1a\x1d\x7f\x8c\x17\x91\xdam\x1bO\x13m\xc0\xe2&y\xea\xfe\xaaX\xd6M~/\xaa\xd5\x89\x0e\x9d\x9c"
33+
expected_cid_multibase = (
34+
"bafyreifwqenb274mc6i5u3i3j4jw3qhcez46v7vkldle27rpvlkysdu5tq"
35+
)
3036

3137
cid = libipld.decode_cid(raw_cid)
3238
cid_multibase = libipld.encode_cid(raw_cid)
@@ -38,4 +44,4 @@ def test_cid_encode_raw() -> None:
3844
assert cid == cid2
3945

4046
# manual encoding for CID v1:
41-
assert expected_cid_multibase == libipld.encode_multibase('b', raw_cid)
47+
assert expected_cid_multibase == libipld.encode_multibase("b", raw_cid)

python/libipld/__init__.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
from ._libipld import (
2+
decode_cid,
3+
encode_cid,
4+
decode_car,
5+
decode_dag_cbor,
6+
decode_dag_cbor_multi,
7+
)
8+
9+
__all__ = [
10+
"decode_cid",
11+
"encode_cid",
12+
"decode_car",
13+
"decode_dag_cbor",
14+
"decode_dag_cbor_multi",
15+
]
2.37 MB
Binary file not shown.

python/libipld/_libipld.pyi

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
from __future__ import annotations
2+
from typing import Any
3+
4+
def decode_cid(data: str | bytes) -> dict[str, Any]: ...
5+
def encode_cid(data: bytes) -> str: ...
6+
def decode_car(data: bytes) -> tuple[dict[str, Any], dict[bytes, dict[str, Any]]]: ...
7+
def decode_dag_cbor(data: bytes) -> dict[str, Any]: ...
8+
def decode_dag_cbor_multi(data: bytes) -> list[dict[str, Any]]: ...
9+
def encode_dag_cbor(data: dict[str, Any]) -> bytes: ...
10+
def decode_multibase(data: str) -> tuple[str, bytes]: ...
11+
def encode_multibase(code: str, data: str | bytes) -> str: ...

src/lib.rs

Lines changed: 38 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@ use ::libipld::cid::{Cid, Error as CidError, Result as CidResult, Version};
77
use anyhow::{anyhow, Result};
88
use byteorder::{BigEndian, ByteOrder};
99
use multihash::Multihash;
10-
use pyo3::{ffi, prelude::*, types::*, BoundObject, PyObject, Python};
1110
use pyo3::pybacked::PyBackedStr;
11+
use pyo3::{ffi, prelude::*, types::*, BoundObject, PyObject, Python};
1212

1313
fn cid_hash_to_pydict<'py>(py: Python<'py>, cid: &Cid) -> Bound<'py, PyDict> {
1414
let hash = cid.hash();
@@ -17,7 +17,7 @@ fn cid_hash_to_pydict<'py>(py: Python<'py>, cid: &Cid) -> Bound<'py, PyDict> {
1717
dict_obj.set_item("code", hash.code()).unwrap();
1818
dict_obj.set_item("size", hash.size()).unwrap();
1919
dict_obj
20-
.set_item("digest", PyBytes::new(py, &hash.digest()))
20+
.set_item("digest", PyBytes::new(py, hash.digest()))
2121
.unwrap();
2222

2323
dict_obj
@@ -74,7 +74,7 @@ fn sort_map_keys(keys: &Bound<PyList>, len: usize) -> Vec<(PyBackedStr, usize)>
7474
if s1.len() != s2.len() {
7575
s1.len().cmp(&s2.len())
7676
} else {
77-
s1.cmp(&s2)
77+
s1.cmp(s2)
7878
}
7979
});
8080

@@ -100,7 +100,8 @@ fn string_new_bound<'py>(py: Python<'py>, s: &[u8]) -> Bound<'py, PyString> {
100100
let ptr = s.as_ptr() as *const c_char;
101101
let len = s.len() as ffi::Py_ssize_t;
102102
unsafe {
103-
Bound::from_owned_ptr(py, ffi::PyUnicode_FromStringAndSize(ptr, len)).downcast_into_unchecked()
103+
Bound::from_owned_ptr(py, ffi::PyUnicode_FromStringAndSize(ptr, len))
104+
.downcast_into_unchecked()
104105
}
105106
}
106107

@@ -113,7 +114,8 @@ fn decode_dag_cbor_to_pyobject<R: Read + Seek>(
113114
if depth > ffi::Py_GetRecursionLimit() as usize {
114115
PyErr::new::<pyo3::exceptions::PyRecursionError, _>(
115116
"RecursionError: maximum recursion depth exceeded in DAG-CBOR decoding",
116-
).restore(py);
117+
)
118+
.restore(py);
117119

118120
return Err(anyhow!("Maximum recursion depth exceeded"));
119121
}
@@ -122,14 +124,20 @@ fn decode_dag_cbor_to_pyobject<R: Read + Seek>(
122124
let major = decode::read_major(r)?;
123125
Ok(match major.kind() {
124126
MajorKind::UnsignedInt => decode::read_uint(r, major)?.into_pyobject(py)?.into(),
125-
MajorKind::NegativeInt => (-1 - decode::read_uint(r, major)? as i64).into_pyobject(py)?.into(),
127+
MajorKind::NegativeInt => (-1 - decode::read_uint(r, major)? as i64)
128+
.into_pyobject(py)?
129+
.into(),
126130
MajorKind::ByteString => {
127131
let len = decode::read_uint(r, major)?;
128-
PyBytes::new(py, &decode::read_bytes(r, len)?).into_pyobject(py)?.into()
132+
PyBytes::new(py, &decode::read_bytes(r, len)?)
133+
.into_pyobject(py)?
134+
.into()
129135
}
130136
MajorKind::TextString => {
131137
let len = decode::read_uint(r, major)?;
132-
string_new_bound(py, &decode::read_bytes(r, len)?).into_pyobject(py)?.into()
138+
string_new_bound(py, &decode::read_bytes(r, len)?)
139+
.into_pyobject(py)?
140+
.into()
133141
}
134142
MajorKind::Array => {
135143
let len: ffi::Py_ssize_t = decode_len(decode::read_uint(r, major)?)?.try_into()?;
@@ -138,10 +146,15 @@ fn decode_dag_cbor_to_pyobject<R: Read + Seek>(
138146
let ptr = ffi::PyList_New(len);
139147

140148
for i in 0..len {
141-
ffi::PyList_SET_ITEM(ptr, i, decode_dag_cbor_to_pyobject(py, r, depth + 1)?.into_ptr());
149+
ffi::PyList_SET_ITEM(
150+
ptr,
151+
i,
152+
decode_dag_cbor_to_pyobject(py, r, depth + 1)?.into_ptr(),
153+
);
142154
}
143155

144-
let list: Bound<'_, PyList> = Bound::from_owned_ptr(py, ptr).downcast_into_unchecked();
156+
let list: Bound<'_, PyList> =
157+
Bound::from_owned_ptr(py, ptr).downcast_into_unchecked();
145158
list.into_pyobject(py)?.into()
146159
}
147160
}
@@ -199,7 +212,7 @@ fn decode_dag_cbor_to_pyobject<R: Read + Seek>(
199212
}
200213

201214
fn encode_dag_cbor_from_pyobject<'py, W: Write>(
202-
py: Python<'py>,
215+
_py: Python<'py>,
203216
obj: &Bound<'py, PyAny>,
204217
w: &mut W,
205218
) -> Result<()> {
@@ -246,7 +259,7 @@ fn encode_dag_cbor_from_pyobject<'py, W: Write>(
246259
encode::write_u64(w, MajorKind::Array, len as u64)?;
247260

248261
for i in 0..len {
249-
encode_dag_cbor_from_pyobject(py, &l.get_item(i)?, w)?;
262+
encode_dag_cbor_from_pyobject(_py, &l.get_item(i)?, w)?;
250263
}
251264

252265
Ok(())
@@ -262,7 +275,7 @@ fn encode_dag_cbor_from_pyobject<'py, W: Write>(
262275
encode::write_u64(w, MajorKind::TextString, key_buf.len() as u64)?;
263276
w.write_all(key_buf)?;
264277

265-
encode_dag_cbor_from_pyobject(py, &values.get_item(i)?, w)?;
278+
encode_dag_cbor_from_pyobject(_py, &values.get_item(i)?, w)?;
266279
}
267280

268281
Ok(())
@@ -280,7 +293,7 @@ fn encode_dag_cbor_from_pyobject<'py, W: Write>(
280293
} else if let Ok(b) = obj.downcast::<PyBytes>() {
281294
// FIXME (MarshalX): it's not efficient to try to parse it as CID
282295
let cid = Cid::try_from(b.as_bytes());
283-
if let Ok(_) = cid {
296+
if cid.is_ok() {
284297
let buf = b.as_bytes();
285298
let len = buf.len();
286299

@@ -332,7 +345,7 @@ fn read_u64_leb128<R: Read>(r: &mut R) -> Result<u64> {
332345

333346
loop {
334347
let mut buf = [0];
335-
if let Err(_) = r.read_exact(&mut buf) {
348+
if r.read_exact(&mut buf).is_err() {
336349
return Err(anyhow!("Unexpected EOF while reading ULEB128 number."));
337350
}
338351

@@ -377,7 +390,7 @@ fn read_cid_from_bytes<R: Read>(r: &mut R) -> CidResult<Cid> {
377390
pub fn decode_car<'py>(py: Python<'py>, data: &[u8]) -> PyResult<(PyObject, Bound<'py, PyDict>)> {
378391
let buf = &mut BufReader::new(Cursor::new(data));
379392

380-
if let Err(_) = read_u64_leb128(buf) {
393+
if read_u64_leb128(buf).is_err() {
381394
return Err(get_err(
382395
"Failed to read CAR header",
383396
"Invalid uvarint".to_string(),
@@ -423,7 +436,7 @@ pub fn decode_car<'py>(py: Python<'py>, data: &[u8]) -> PyResult<(PyObject, Boun
423436
let parsed_blocks = PyDict::new(py);
424437

425438
loop {
426-
if let Err(_) = read_u64_leb128(buf) {
439+
if read_u64_leb128(buf).is_err() {
427440
// FIXME (MarshalX): we are not raising an error here because of possible EOF
428441
break;
429442
}
@@ -472,7 +485,7 @@ pub fn decode_dag_cbor(py: Python, data: &[u8]) -> PyResult<PyObject> {
472485

473486
if let Some(py_err) = PyErr::take(py) {
474487
py_err.set_cause(py, Option::from(err));
475-
// in case something set global interpreters error,
488+
// in case something set global interpreter's error,
476489
// for example C FFI function, we should return it
477490
// the real case: RecursionError (set by Py_EnterRecursiveCall)
478491
Err(py_err)
@@ -494,9 +507,10 @@ pub fn encode_dag_cbor<'py>(
494507
if let Err(e) = buf.flush() {
495508
return Err(get_err("Failed to flush buffer", e.to_string()));
496509
}
497-
Ok(PyBytes::new(py, &buf.get_ref()))
510+
Ok(PyBytes::new(py, buf.get_ref()))
498511
}
499512

513+
#[allow(clippy::extra_unused_lifetimes)]
500514
fn get_cid_from_py_any<'py>(data: &Bound<PyAny>) -> PyResult<Cid> {
501515
let cid: CidResult<Cid>;
502516
if let Ok(s) = data.downcast::<PyString>() {
@@ -522,7 +536,10 @@ fn decode_cid<'py>(py: Python<'py>, data: &Bound<PyAny>) -> PyResult<Bound<'py,
522536

523537
#[pyfunction]
524538
fn encode_cid<'py>(py: Python<'py>, data: &Bound<PyAny>) -> PyResult<Bound<'py, PyString>> {
525-
Ok(PyString::new(py, get_cid_from_py_any(data)?.to_string().as_str()))
539+
Ok(PyString::new(
540+
py,
541+
get_cid_from_py_any(data)?.to_string().as_str(),
542+
))
526543
}
527544

528545
#[pyfunction]
@@ -557,6 +574,7 @@ fn get_err(msg: &str, err: String) -> PyErr {
557574
}
558575

559576
#[pymodule]
577+
#[pyo3(name = "_libipld")]
560578
fn libipld(m: &Bound<'_, PyModule>) -> PyResult<()> {
561579
m.add_function(wrap_pyfunction!(decode_cid, m)?)?;
562580
m.add_function(wrap_pyfunction!(encode_cid, m)?)?;

0 commit comments

Comments
 (0)