Skip to content

Commit bc3caf5

Browse files
authored
release: 0.26 (#95)
* release: 0.26 * use trusted publishing for release
1 parent e64436c commit bc3caf5

File tree

9 files changed

+87
-64
lines changed

9 files changed

+87
-64
lines changed

.github/workflows/release.yml

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,29 @@ on:
44
push:
55
tags:
66
- "v*"
7+
workflow_dispatch:
8+
inputs:
9+
version:
10+
description: The version to build
711

812
jobs:
913
release:
14+
permissions:
15+
id-token: write
16+
1017
runs-on: ubuntu-latest
1118
environment: release
1219
steps:
13-
- name: Checkout repository
14-
uses: actions/checkout@v4
20+
- uses: actions/checkout@v5
21+
with:
22+
# The tag to build or the tag received by the tag event
23+
ref: ${{ github.event.inputs.version || github.ref }}
24+
persist-credentials: false
1525

16-
- uses: dtolnay/rust-toolchain@stable
26+
- uses: rust-lang/crates-io-auth-action@v1
27+
id: auth
1728

1829
- name: Publish to crates.io
1930
run: cargo publish
2031
env:
21-
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
32+
CARGO_REGISTRY_TOKEN: ${{ steps.auth.outputs.token }}

CHANGELOG.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,17 @@
1+
## 0.26.0 - 2025-08-30
2+
3+
### Packaging
4+
- Bump MSRV to 1.74
5+
- Update to PyO3 0.26
6+
7+
### Changed
8+
- `PythonizeTypes`, `PythonizeMappingType` and `PythonizeNamedMappingType` no longer have a lifetime on the trait, instead the `Builder` type is a GAT.
9+
10+
## 0.25.0 - 2025-05-23
11+
12+
### Packaging
13+
- Update to PyO3 0.25
14+
115
## 0.24.0 - 2025-03-26
216

317
### Packaging

Cargo.toml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
[package]
22
name = "pythonize"
3-
version = "0.25.0"
3+
version = "0.26.0"
44
authors = ["David Hewitt <1939362+davidhewitt@users.noreply.github.com>"]
55
edition = "2021"
6-
rust-version = "1.65"
6+
rust-version = "1.74"
77
license = "MIT"
88
description = "Serde Serializer & Deserializer from Rust <--> Python, backed by PyO3."
99
homepage = "https://github.com/davidhewitt/pythonize"
@@ -13,11 +13,11 @@ documentation = "https://docs.rs/crate/pythonize/"
1313

1414
[dependencies]
1515
serde = { version = "1.0", default-features = false, features = ["std"] }
16-
pyo3 = { version = "0.25", default-features = false }
16+
pyo3 = { version = "0.26", default-features = false }
1717

1818
[dev-dependencies]
1919
serde = { version = "1.0", default-features = false, features = ["derive"] }
20-
pyo3 = { version = "0.25", default-features = false, features = ["auto-initialize", "macros", "py-clone"] }
20+
pyo3 = { version = "0.26", default-features = false, features = ["auto-initialize", "macros", "py-clone"] }
2121
serde_json = "1.0"
2222
serde_bytes = "0.11"
2323
maplit = "1.0.2"

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ let sample = Sample {
3535
bar: None
3636
};
3737

38-
Python::with_gil(|py| {
38+
Python::attach(|py| {
3939
// Rust -> Python
4040
let obj = pythonize(py, &sample).unwrap();
4141

src/de.rs

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ impl<'a, 'py> Depythonizer<'a, 'py> {
2424
}
2525

2626
fn sequence_access(&self, expected_len: Option<usize>) -> Result<PySequenceAccess<'a, 'py>> {
27-
let seq = self.input.downcast::<PySequence>()?;
27+
let seq = self.input.cast::<PySequence>()?;
2828
let len = self.input.len()?;
2929

3030
match expected_len {
@@ -36,10 +36,10 @@ impl<'a, 'py> Depythonizer<'a, 'py> {
3636
}
3737

3838
fn set_access(&self) -> Result<PySetAsSequence<'py>> {
39-
match self.input.downcast::<PySet>() {
39+
match self.input.cast::<PySet>() {
4040
Ok(set) => Ok(PySetAsSequence::from_set(set)),
4141
Err(e) => {
42-
if let Ok(f) = self.input.downcast::<PyFrozenSet>() {
42+
if let Ok(f) = self.input.cast::<PyFrozenSet>() {
4343
Ok(PySetAsSequence::from_frozenset(f))
4444
} else {
4545
Err(e.into())
@@ -49,7 +49,7 @@ impl<'a, 'py> Depythonizer<'a, 'py> {
4949
}
5050

5151
fn dict_access(&self) -> Result<PyMappingAccess<'py>> {
52-
PyMappingAccess::new(self.input.downcast()?)
52+
PyMappingAccess::new(self.input.cast()?)
5353
}
5454

5555
fn deserialize_any_int<'de, V>(&self, int: &Bound<'_, PyInt>, visitor: V) -> Result<V::Value>
@@ -111,7 +111,7 @@ impl<'de> de::Deserializer<'de> for &'_ mut Depythonizer<'_, '_> {
111111
self.deserialize_unit(visitor)
112112
} else if obj.is_instance_of::<PyBool>() {
113113
self.deserialize_bool(visitor)
114-
} else if let Ok(x) = obj.downcast::<PyInt>() {
114+
} else if let Ok(x) = obj.cast::<PyInt>() {
115115
self.deserialize_any_int(x, visitor)
116116
} else if obj.is_instance_of::<PyList>() || obj.is_instance_of::<PyTuple>() {
117117
self.deserialize_tuple(obj.len()?, visitor)
@@ -128,9 +128,9 @@ impl<'de> de::Deserializer<'de> for &'_ mut Depythonizer<'_, '_> {
128128
self.deserialize_f64(visitor)
129129
} else if obj.is_instance_of::<PyFrozenSet>() || obj.is_instance_of::<PySet>() {
130130
self.deserialize_seq(visitor)
131-
} else if obj.downcast::<PySequence>().is_ok() {
131+
} else if obj.cast::<PySequence>().is_ok() {
132132
self.deserialize_tuple(obj.len()?, visitor)
133-
} else if obj.downcast::<PyMapping>().is_ok() {
133+
} else if obj.cast::<PyMapping>().is_ok() {
134134
self.deserialize_map(visitor)
135135
} else {
136136
Err(obj.get_type().qualname().map_or_else(
@@ -151,7 +151,7 @@ impl<'de> de::Deserializer<'de> for &'_ mut Depythonizer<'_, '_> {
151151
where
152152
V: de::Visitor<'de>,
153153
{
154-
let s = self.input.downcast::<PyString>()?.to_cow()?;
154+
let s = self.input.cast::<PyString>()?.to_cow()?;
155155
if s.len() != 1 {
156156
return Err(PythonizeError::invalid_length_char());
157157
}
@@ -175,7 +175,7 @@ impl<'de> de::Deserializer<'de> for &'_ mut Depythonizer<'_, '_> {
175175
where
176176
V: de::Visitor<'de>,
177177
{
178-
let s = self.input.downcast::<PyString>()?;
178+
let s = self.input.cast::<PyString>()?;
179179
visitor.visit_str(&s.to_cow()?)
180180
}
181181

@@ -190,7 +190,7 @@ impl<'de> de::Deserializer<'de> for &'_ mut Depythonizer<'_, '_> {
190190
where
191191
V: de::Visitor<'de>,
192192
{
193-
let b = self.input.downcast::<PyBytes>()?;
193+
let b = self.input.cast::<PyBytes>()?;
194194
visitor.visit_bytes(b.as_bytes())
195195
}
196196

@@ -303,17 +303,17 @@ impl<'de> de::Deserializer<'de> for &'_ mut Depythonizer<'_, '_> {
303303
V: de::Visitor<'de>,
304304
{
305305
let item = &self.input;
306-
if let Ok(s) = item.downcast::<PyString>() {
306+
if let Ok(s) = item.cast::<PyString>() {
307307
visitor.visit_enum(s.to_cow()?.into_deserializer())
308-
} else if let Ok(m) = item.downcast::<PyMapping>() {
308+
} else if let Ok(m) = item.cast::<PyMapping>() {
309309
// Get the enum variant from the mapping key
310310
if m.len()? != 1 {
311311
return Err(PythonizeError::invalid_length_enum());
312312
}
313313
let variant: Bound<PyString> = m
314314
.keys()?
315315
.get_item(0)?
316-
.downcast_into::<PyString>()
316+
.cast_into::<PyString>()
317317
.map_err(|_| PythonizeError::dict_key_not_string())?;
318318
let value = m.get_item(&variant)?;
319319
visitor.visit_enum(PyEnumAccess::new(&value, variant))
@@ -328,7 +328,7 @@ impl<'de> de::Deserializer<'de> for &'_ mut Depythonizer<'_, '_> {
328328
{
329329
let s = self
330330
.input
331-
.downcast::<PyString>()
331+
.cast::<PyString>()
332332
.map_err(|_| PythonizeError::dict_key_not_string())?;
333333
visitor.visit_str(&s.to_cow()?)
334334
}
@@ -528,7 +528,7 @@ mod test {
528528
where
529529
T: de::DeserializeOwned + PartialEq + std::fmt::Debug,
530530
{
531-
Python::with_gil(|py| {
531+
Python::attach(|py| {
532532
let obj = py.eval(code, None, None).unwrap();
533533
let actual: T = depythonize(&obj).unwrap();
534534
assert_eq!(&actual, expected);
@@ -585,7 +585,7 @@ mod test {
585585

586586
let code = c_str!("{'foo': 'Foo'}");
587587

588-
Python::with_gil(|py| {
588+
Python::attach(|py| {
589589
let locals = PyDict::new(py);
590590
let obj = py.eval(code, None, Some(&locals)).unwrap();
591591
assert!(matches!(
@@ -613,7 +613,7 @@ mod test {
613613

614614
let code = c_str!("('cat', -10.05, 'foo')");
615615

616-
Python::with_gil(|py| {
616+
Python::attach(|py| {
617617
let locals = PyDict::new(py);
618618
let obj = py.eval(code, None, Some(&locals)).unwrap();
619619
assert!(matches!(
@@ -825,7 +825,7 @@ mod test {
825825

826826
#[test]
827827
fn test_int_limits() {
828-
Python::with_gil(|py| {
828+
Python::attach(|py| {
829829
// serde_json::Value supports u64 and i64 as maximum sizes
830830
let _: serde_json::Value = depythonize(&u8::MAX.into_pyobject(py).unwrap()).unwrap();
831831
let _: serde_json::Value = depythonize(&u8::MIN.into_pyobject(py).unwrap()).unwrap();
@@ -857,7 +857,7 @@ mod test {
857857

858858
#[test]
859859
fn test_deserialize_bytes() {
860-
Python::with_gil(|py| {
860+
Python::attach(|py| {
861861
let obj = PyBytes::new(py, "hello".as_bytes());
862862
let actual: Vec<u8> = depythonize(&obj).unwrap();
863863
assert_eq!(actual, b"hello");
@@ -874,7 +874,7 @@ mod test {
874874

875875
#[test]
876876
fn test_unknown_type() {
877-
Python::with_gil(|py| {
877+
Python::attach(|py| {
878878
let obj = py
879879
.import("decimal")
880880
.unwrap()

src/error.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ pub enum ErrorImpl {
7373
Message(String),
7474
/// A Python type not supported by the deserializer
7575
UnsupportedType(String),
76-
/// A `PyAny` object that failed to downcast to an expected Python type
76+
/// A `PyAny` object that failed to cast to an expected Python type
7777
UnexpectedType(String),
7878
/// Dict keys should be strings to deserialize to struct fields
7979
DictKeyNotString,

src/ser.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -632,7 +632,7 @@ mod test {
632632
where
633633
T: Serialize,
634634
{
635-
Python::with_gil(|py| -> PyResult<()> {
635+
Python::attach(|py| -> PyResult<()> {
636636
let obj = pythonize(py, &src)?;
637637

638638
let locals = PyDict::new(py);
@@ -845,7 +845,7 @@ mod test {
845845
// serde treats &[u8] as a sequence of integers due to lack of specialization
846846
test_ser(b"foo", "[102,111,111]");
847847

848-
Python::with_gil(|py| {
848+
Python::attach(|py| {
849849
assert!(pythonize(py, serde_bytes::Bytes::new(b"foo"))
850850
.expect("bytes will always serialize successfully")
851851
.eq(&PyBytes::new(py, b"foo"))

0 commit comments

Comments
 (0)