feat: update pyo3 (#308)

* feat: update pyo3

* update workflows

* python version

* update minimum

* update pyproject

* fix pipeline
This commit is contained in:
stefan-gorules
2025-02-07 13:12:46 +01:00
committed by GitHub
parent b992e7ccef
commit be305a4fba
13 changed files with 145 additions and 111 deletions
+4 -4
View File
@@ -55,7 +55,7 @@ jobs:
run: dir ..\..\target\${{ matrix.settings.target }}\release
- name: Upload artifact
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
if: ${{ matrix.settings.target != 'x86_64-pc-windows-msvc' }}
with:
name: ${{ matrix.settings.target }}
@@ -63,7 +63,7 @@ jobs:
if-no-files-found: error
- name: Upload artifact Windows
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
if: ${{ matrix.settings.target == 'x86_64-pc-windows-msvc' }}
with:
name: ${{ matrix.settings.target }}
@@ -76,7 +76,7 @@ jobs:
- uses: actions/checkout@v3
- name: Upload artifact
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: common
path: bindings/c/zen_engine.h
@@ -95,7 +95,7 @@ jobs:
persist-credentials: false
token: ${{ secrets.PAT }}
- name: Download all artifacts
uses: actions/download-artifact@v3
uses: actions/download-artifact@v4
with:
path: artifacts
+7 -7
View File
@@ -160,7 +160,7 @@ jobs:
shell: bash
- name: Upload artifact
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: bindings-${{ matrix.settings.target }}
path: bindings/nodejs/*.node
@@ -203,7 +203,7 @@ jobs:
run: yarn install --immutable --mode=skip-build
- name: Download artifacts
uses: actions/download-artifact@v3
uses: actions/download-artifact@v4
with:
name: bindings-${{ matrix.settings.target }}
path: artifacts
@@ -245,7 +245,7 @@ jobs:
run: yarn install --immutable --mode=skip-build
- name: Download artifacts
uses: actions/download-artifact@v3
uses: actions/download-artifact@v4
with:
name: bindings-x86_64-unknown-linux-gnu
path: artifacts
@@ -296,7 +296,7 @@ jobs:
run: yarn install --immutable --mode=skip-build
- name: Download artifacts
uses: actions/download-artifact@v3
uses: actions/download-artifact@v4
with:
name: bindings-x86_64-unknown-linux-musl
path: artifacts
@@ -354,7 +354,7 @@ jobs:
- run: docker run --rm --privileged multiarch/qemu-user-static --reset -p yes
- name: Download artifacts
uses: actions/download-artifact@v3
uses: actions/download-artifact@v4
with:
name: bindings-aarch64-unknown-linux-musl
path: artifacts
@@ -399,7 +399,7 @@ jobs:
# pwd
#
# - name: Download artifacts
# uses: actions/download-artifact@v3
# uses: actions/download-artifact@v4
# with:
# name: bindings-aarch64-unknown-linux-gnu
# path: artifacts
@@ -461,7 +461,7 @@ jobs:
run: yarn install --immutable --mode=skip-build
- name: Download all artifacts
uses: actions/download-artifact@v3
uses: actions/download-artifact@v4
with:
path: artifacts
+25 -25
View File
@@ -31,13 +31,12 @@ jobs:
working-directory: ${{ env.WORKING_DIRECTORY }}
strategy:
matrix:
# target: [x86_64, x86, aarch64, armv7, s390x, ppc64le]
target: [x86_64, aarch64]
target: [ x86_64, aarch64 ]
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
with:
python-version: '3.10'
python-version: '3.13'
- name: Build wheels
uses: stefan-gorules/maturin-action@main
@@ -50,10 +49,10 @@ jobs:
apt install -y patch
- name: Upload wheels
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: wheels
path: dist
name: wheels-linux-${{ matrix.target }}
path: wheels
windows:
runs-on: windows-latest
@@ -62,12 +61,12 @@ jobs:
working-directory: ${{ env.WORKING_DIRECTORY }}
strategy:
matrix:
target: [x64]
target: [ x64 ]
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
with:
python-version: '3.10'
python-version: '3.13'
architecture: ${{ matrix.target }}
- name: Build wheels
uses: PyO3/maturin-action@v1
@@ -76,10 +75,10 @@ jobs:
args: --release --out dist --find-interpreter -m bindings/python/Cargo.toml
sccache: 'true'
- name: Upload wheels
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: wheels
path: dist
name: wheels-windows-${{ matrix.target }}
path: wheels
macos:
runs-on: macos-latest
@@ -88,12 +87,12 @@ jobs:
working-directory: ${{ env.WORKING_DIRECTORY }}
strategy:
matrix:
target: [x86_64, aarch64]
target: [ x86_64, aarch64 ]
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
with:
python-version: '3.10'
python-version: '3.13'
- name: Build wheels
uses: PyO3/maturin-action@v1
with:
@@ -101,10 +100,10 @@ jobs:
args: --release --out dist --find-interpreter -m bindings/python/Cargo.toml
sccache: 'true'
- name: Upload wheels
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: wheels
path: dist
name: wheels-macos-${{ matrix.target }}
path: wheels
sdist:
runs-on: ubuntu-latest
@@ -113,6 +112,9 @@ jobs:
working-directory: ${{ env.WORKING_DIRECTORY }}
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
with:
python-version: '3.13'
- name: Build sdist
uses: PyO3/maturin-action@v1
@@ -120,10 +122,10 @@ jobs:
command: sdist
args: --out dist -m bindings/python/Cargo.toml
- name: Upload sdist
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: wheels
path: dist
name: wheels-sdist
path: wheels
release:
name: Release
@@ -131,14 +133,12 @@ jobs:
defaults:
run:
working-directory: ${{ env.WORKING_DIRECTORY }}
# if: "startsWith(github.ref, 'refs/tags/')"
needs: [linux, windows, macos, sdist]
# needs: [linux, sdist]
# needs: [linux]
needs: [ linux, windows, macos, sdist ]
steps:
- uses: actions/download-artifact@v3
- uses: actions/download-artifact@v4
with:
name: wheels
path: wheels
- name: Publish to PyPI
if: "startsWith(github.event.head_commit.message, 'chore(release): publish python')"
uses: PyO3/maturin-action@v1
+3 -3
View File
@@ -12,9 +12,9 @@ crate-type = ["cdylib"]
[dependencies]
anyhow = { workspace = true }
either = "1.13"
pyo3 = { version = "0.20", features = ["anyhow", "serde"] }
pyo3-asyncio = { version = "0.20", features = ["tokio-runtime"] }
pythonize = "0.20"
pyo3 = { version = "0.23", features = ["anyhow", "serde"] }
pyo3-async-runtimes = { version = "0.23", features = ["tokio-runtime"] }
pythonize = "0.23"
json_dotpath = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }
+1 -1
View File
@@ -1,5 +1,5 @@
[build-system]
requires = ["maturin>=0.14,<0.15"]
requires = ["maturin>=1,<2"]
build-backend = "maturin"
[project]
+8 -8
View File
@@ -1,8 +1,8 @@
use anyhow::anyhow;
use either::Either;
use pyo3::types::PyDict;
use pyo3::{PyObject, PyResult, Python};
use pyo3_asyncio::tokio;
use pyo3::{Bound, IntoPyObjectExt, Py, PyAny, PyObject, PyResult, Python};
use pyo3_async_runtimes::tokio;
use pythonize::depythonize;
use zen_engine::handler::custom_node_adapter::{CustomNodeAdapter, CustomNodeRequest};
@@ -11,10 +11,10 @@ use zen_engine::handler::node::{NodeResponse, NodeResult};
use crate::types::PyNodeRequest;
#[derive(Default)]
pub(crate) struct PyCustomNode(Option<PyObject>);
pub(crate) struct PyCustomNode(Option<Py<PyAny>>);
impl From<PyObject> for PyCustomNode {
fn from(value: PyObject) -> Self {
impl From<Py<PyAny>> for PyCustomNode {
fn from(value: Py<PyAny>) -> Self {
Self(Some(value))
}
}
@@ -26,8 +26,8 @@ impl From<Option<PyObject>> for PyCustomNode {
}
fn extract_custom_node_response(py: Python<'_>, result: PyObject) -> NodeResult {
let dict = result.extract::<&PyDict>(py)?;
let response: NodeResponse = depythonize(dict)?;
let dict = result.extract::<Bound<'_, PyDict>>(py)?;
let response: NodeResponse = depythonize(&dict)?;
Ok(response)
}
@@ -45,7 +45,7 @@ impl CustomNodeAdapter for PyCustomNode {
return Ok(Either::Left(extract_custom_node_response(py, result)));
}
let result_future = tokio::into_future(result.as_ref(py))?;
let result_future = tokio::into_future(result.into_bound_py_any(py)?)?;
return Ok(Either::Right(result_future));
});
+19 -10
View File
@@ -2,8 +2,8 @@ use std::sync::Arc;
use anyhow::{anyhow, Context};
use pyo3::types::PyDict;
use pyo3::{pyclass, pymethods, PyAny, PyObject, PyResult, Python, ToPyObject};
use pyo3_asyncio::tokio;
use pyo3::{pyclass, pymethods, Bound, IntoPyObjectExt, Py, PyAny, PyResult, Python};
use pyo3_async_runtimes::tokio;
use pythonize::depythonize;
use serde_json::Value;
use zen_engine::{Decision, EvaluationOptions};
@@ -25,7 +25,13 @@ impl From<Decision<PyDecisionLoader, PyCustomNode>> for PyZenDecision {
#[pymethods]
impl PyZenDecision {
pub fn evaluate(&self, py: Python, ctx: &PyDict, opts: Option<&PyDict>) -> PyResult<PyObject> {
#[pyo3(signature = (ctx, opts=None))]
pub fn evaluate(
&self,
py: Python,
ctx: &Bound<'_, PyDict>,
opts: Option<&Bound<'_, PyDict>>,
) -> PyResult<Py<PyAny>> {
let context: Value = depythonize(ctx).context("Failed to convert dict")?;
let options: PyZenEvaluateOptions = if let Some(op) = opts {
depythonize(op).context("Failed to convert dict")?
@@ -46,15 +52,16 @@ impl PyZenDecision {
})?;
let value = serde_json::to_value(&result).context("Fail")?;
Ok(PyValue(value).to_object(py))
PyValue(value).into_py_any(py)
}
#[pyo3(signature = (ctx, opts=None))]
pub fn async_evaluate<'py>(
&'py self,
py: Python<'py>,
ctx: &PyDict,
opts: Option<&PyDict>,
) -> PyResult<&PyAny> {
ctx: &Bound<'_, PyDict>,
opts: Option<&Bound<'_, PyDict>>,
) -> PyResult<Py<PyAny>> {
let context: Value = depythonize(ctx).context("Failed to convert dict")?;
let options: PyZenEvaluateOptions = if let Some(op) = opts {
depythonize(op).context("Failed to convert dict")?
@@ -63,7 +70,7 @@ impl PyZenDecision {
};
let decision = self.0.clone();
tokio::future_into_py(py, async move {
let result = tokio::future_into_py(py, async move {
let result = futures::executor::block_on(decision.evaluate_with_opts(
context.into(),
EvaluationOptions {
@@ -77,8 +84,10 @@ impl PyZenDecision {
let value = serde_json::to_value(result).context("Failed to serialize result")?;
Python::with_gil(|py| Ok(PyValue(value).to_object(py)))
})
Python::with_gil(|py| PyValue(value).into_py_any(py))
})?;
Ok(result.unbind())
}
pub fn validate(&self) -> PyResult<()> {
+21 -15
View File
@@ -1,9 +1,10 @@
use std::sync::Arc;
use anyhow::{anyhow, Context};
use pyo3::prelude::PyDictMethods;
use pyo3::types::PyDict;
use pyo3::{pyclass, pymethods, PyAny, PyObject, PyResult, Python, ToPyObject};
use pyo3_asyncio::tokio;
use pyo3::{pyclass, pymethods, Bound, IntoPyObjectExt, Py, PyAny, PyResult, Python};
use pyo3_async_runtimes::tokio;
use pythonize::depythonize;
use serde::{Deserialize, Serialize};
use serde_json::Value;
@@ -51,18 +52,19 @@ impl Default for PyZenEngine {
#[pymethods]
impl PyZenEngine {
#[new]
pub fn new(maybe_options: Option<&PyDict>) -> PyResult<Self> {
#[pyo3(signature = (maybe_options=None))]
pub fn new(maybe_options: Option<&Bound<'_, PyDict>>) -> PyResult<Self> {
let Some(options) = maybe_options else {
return Ok(Default::default());
};
let loader = match options.get_item("loader")? {
Some(loader) => Some(Python::with_gil(|py| loader.to_object(py))),
Some(loader) => Some(Python::with_gil(|py| loader.into_py_any(py))?),
None => None,
};
let custom_node = match options.get_item("customHandler")? {
Some(custom_node) => Some(Python::with_gil(|py| custom_node.to_object(py))),
Some(custom_node) => Some(Python::with_gil(|py| custom_node.into_py_any(py))?),
None => None,
};
@@ -75,13 +77,14 @@ impl PyZenEngine {
})
}
#[pyo3(signature = (key, ctx, opts=None))]
pub fn evaluate(
&self,
py: Python,
key: String,
ctx: &PyDict,
opts: Option<&PyDict>,
) -> PyResult<PyObject> {
ctx: &Bound<'_, PyDict>,
opts: Option<&Bound<'_, PyDict>>,
) -> PyResult<Py<PyAny>> {
let context: Value = depythonize(ctx).context("Failed to convert dict")?;
let options: PyZenEvaluateOptions = if let Some(op) = opts {
depythonize(op).context("Failed to convert dict")?
@@ -103,16 +106,17 @@ impl PyZenEngine {
})?;
let value = serde_json::to_value(&result).context("Failed to serialize result")?;
Ok(PyValue(value).to_object(py))
PyValue(value).into_py_any(py)
}
#[pyo3(signature = (key, ctx, opts=None))]
pub fn async_evaluate<'py>(
&'py self,
py: Python<'py>,
key: String,
ctx: &PyDict,
opts: Option<&PyDict>,
) -> PyResult<&PyAny> {
ctx: &Bound<'_, PyDict>,
opts: Option<&Bound<'_, PyDict>>,
) -> PyResult<Py<PyAny>> {
let context: Value = depythonize(ctx).context("Failed to convert dict")?;
let options: PyZenEvaluateOptions = if let Some(op) = opts {
depythonize(op).context("Failed to convert dict")?
@@ -121,7 +125,7 @@ impl PyZenEngine {
};
let graph = self.graph.clone();
tokio::future_into_py(py, async move {
let result = tokio::future_into_py(py, async move {
let result = futures::executor::block_on(graph.evaluate_with_opts(
key,
context.into(),
@@ -136,8 +140,10 @@ impl PyZenEngine {
let value = serde_json::to_value(result).context("Failed to serialize result")?;
Python::with_gil(|py| Ok(PyValue(value).to_object(py)))
})
Python::with_gil(|py| PyValue(value).into_py_any(py))
})?;
Ok(result.unbind())
}
pub fn create_decision(&self, content: String) -> PyResult<PyZenDecision> {
+12 -7
View File
@@ -1,17 +1,18 @@
use anyhow::{anyhow, Context};
use pyo3::types::PyDict;
use pyo3::{pyfunction, PyObject, PyResult, Python, ToPyObject};
use pyo3::{pyfunction, Bound, IntoPyObjectExt, Py, PyAny, PyResult, Python};
use pythonize::depythonize;
use serde_json::Value;
use crate::value::PyValue;
#[pyfunction]
#[pyo3(signature = (expression, ctx=None))]
pub fn evaluate_expression(
py: Python,
expression: String,
ctx: Option<&PyDict>,
) -> PyResult<PyObject> {
ctx: Option<&Bound<'_, PyDict>>,
) -> PyResult<Py<PyAny>> {
let context = ctx
.map(|ctx| depythonize(ctx))
.transpose()
@@ -21,11 +22,11 @@ pub fn evaluate_expression(
let result = zen_expression::evaluate_expression(expression.as_str(), context.into())
.map_err(|e| anyhow!(serde_json::to_string(&e).unwrap_or_else(|_| e.to_string())))?;
Ok(PyValue(result.to_value()).to_object(py))
PyValue(result.to_value()).into_py_any(py)
}
#[pyfunction]
pub fn evaluate_unary_expression(expression: String, ctx: &PyDict) -> PyResult<bool> {
pub fn evaluate_unary_expression(expression: String, ctx: &Bound<'_, PyDict>) -> PyResult<bool> {
let context: Value = depythonize(ctx).context("Failed to convert context")?;
let result = zen_expression::evaluate_unary_expression(expression.as_str(), context.into())
@@ -35,11 +36,15 @@ pub fn evaluate_unary_expression(expression: String, ctx: &PyDict) -> PyResult<b
}
#[pyfunction]
pub fn render_template(py: Python, template: String, ctx: &PyDict) -> PyResult<PyObject> {
pub fn render_template(
py: Python,
template: String,
ctx: &Bound<'_, PyDict>,
) -> PyResult<Py<PyAny>> {
let context: Value = depythonize(ctx).context("Failed to convert context")?;
let result = zen_tmpl::render(template.as_str(), context.into())
.map_err(|e| anyhow!(serde_json::to_string(&e).unwrap_or_else(|_| e.to_string())))?;
Ok(PyValue(result.to_value()).to_object(py))
PyValue(result.to_value()).into_py_any(py)
}
+3 -2
View File
@@ -1,8 +1,9 @@
use crate::decision::PyZenDecision;
use crate::engine::PyZenEngine;
use crate::expression::{evaluate_expression, evaluate_unary_expression, render_template};
use pyo3::prelude::PyModuleMethods;
use pyo3::types::PyModule;
use pyo3::{pymodule, wrap_pyfunction, PyResult, Python};
use pyo3::{pymodule, wrap_pyfunction, Bound, PyResult, Python};
mod custom_node;
mod decision;
@@ -13,7 +14,7 @@ mod types;
mod value;
#[pymodule]
fn zen(_py: Python, m: &PyModule) -> PyResult<()> {
fn zen(_py: Python, m: &Bound<'_, PyModule>) -> PyResult<()> {
m.add_class::<PyZenEngine>()?;
m.add_class::<PyZenDecision>()?;
m.add_function(wrap_pyfunction!(evaluate_expression, m)?)?;
+4 -4
View File
@@ -2,13 +2,13 @@ use std::future::Future;
use std::sync::Arc;
use anyhow::anyhow;
use pyo3::{PyObject, Python};
use pyo3::{Py, PyAny, PyObject, Python};
use zen_engine::loader::{DecisionLoader, LoaderError, LoaderResponse};
use zen_engine::model::DecisionContent;
#[derive(Default)]
pub(crate) struct PyDecisionLoader(Option<PyObject>);
pub(crate) struct PyDecisionLoader(Option<Py<PyAny>>);
impl From<PyObject> for PyDecisionLoader {
fn from(value: PyObject) -> Self {
@@ -16,8 +16,8 @@ impl From<PyObject> for PyDecisionLoader {
}
}
impl From<Option<PyObject>> for PyDecisionLoader {
fn from(value: Option<PyObject>) -> Self {
impl From<Option<Py<PyAny>>> for PyDecisionLoader {
fn from(value: Option<Py<PyAny>>) -> Self {
Self(value)
}
}
+10 -10
View File
@@ -1,6 +1,6 @@
use anyhow::{anyhow, Context};
use json_dotpath::DotPaths;
use pyo3::{pyclass, pymethods, PyObject, PyResult, Python, ToPyObject};
use pyo3::{pyclass, pymethods, IntoPyObjectExt, Py, PyAny, PyResult, Python};
use serde::Serialize;
use serde_json::Value;
use std::sync::Arc;
@@ -37,9 +37,9 @@ pub struct PyNodeRequest {
inner_input: Value,
#[pyo3(get)]
pub input: PyObject,
pub input: Py<PyAny>,
#[pyo3(get)]
pub node: PyObject,
pub node: Py<PyAny>,
}
impl PyNodeRequest {
@@ -48,8 +48,8 @@ impl PyNodeRequest {
let node_val = serde_json::to_value(&inner_node).unwrap();
Ok(Self {
input: value_to_object(py, &value.input.to_value()),
node: value_to_object(py, &node_val),
input: value_to_object(py, &value.input.to_value())?.unbind(),
node: value_to_object(py, &node_val)?.unbind(),
inner_input: value.input.to_value(),
inner_node,
@@ -59,7 +59,7 @@ impl PyNodeRequest {
#[pymethods]
impl PyNodeRequest {
fn get_field(&self, py: Python, path: String) -> PyResult<PyObject> {
fn get_field(&self, py: Python, path: String) -> PyResult<Py<PyAny>> {
let node_config = &self.inner_node.config;
let selected_value: Value = node_config
@@ -68,16 +68,16 @@ impl PyNodeRequest {
.flatten()
.context("Failed to find JSON path")?;
let Value::String(template) = selected_value else {
return Ok(PyValue(selected_value).to_object(py));
return PyValue(selected_value).into_py_any(py);
};
let template_value = zen_tmpl::render(template.as_str(), Variable::from(&self.inner_input))
.map_err(|e| anyhow!(serde_json::to_string(&e).unwrap_or_else(|_| e.to_string())))?;
Ok(PyValue(template_value.to_value()).to_object(py))
PyValue(template_value.to_value()).into_py_any(py)
}
fn get_field_raw(&self, py: Python, path: String) -> PyResult<PyObject> {
fn get_field_raw(&self, py: Python, path: String) -> PyResult<Py<PyAny>> {
let node_config = &self.inner_node.config;
let selected_value: Value = node_config
@@ -86,7 +86,7 @@ impl PyNodeRequest {
.flatten()
.context("Failed to find JSON path")?;
Ok(PyValue(selected_value).to_object(py))
PyValue(selected_value).into_py_any(py)
}
}
+28 -15
View File
@@ -1,35 +1,48 @@
use pyo3::{PyObject, Python, ToPyObject};
use pyo3::prelude::{PyDictMethods, PyListMethods};
use pyo3::types::{PyDict, PyList};
use pyo3::{Bound, IntoPyObject, IntoPyObjectExt, PyAny, PyErr, PyResult, Python};
use serde_json::Value;
use std::collections::HashMap;
#[repr(transparent)]
#[derive(Clone, Debug)]
pub struct PyValue(pub Value);
pub fn value_to_object(py: Python<'_>, val: &Value) -> PyObject {
pub fn value_to_object<'py>(py: Python<'py>, val: &Value) -> PyResult<Bound<'py, PyAny>> {
match val {
Value::Null => py.None(),
Value::Bool(b) => b.to_object(py),
Value::Null => py.None().into_bound_py_any(py),
Value::Bool(b) => b.into_bound_py_any(py),
Value::Number(n) => {
let oi64 = n.as_i64().map(|i| i.to_object(py));
let ou64 = n.as_u64().map(|i| i.to_object(py));
let of64 = n.as_f64().map(|i| i.to_object(py));
let oi64 = n.as_i64().map(|i| i.into_bound_py_any(py));
let ou64 = n.as_u64().map(|i| i.into_bound_py_any(py));
let of64 = n.as_f64().map(|i| i.into_bound_py_any(py));
oi64.or(ou64).or(of64).expect("number too large")
}
Value::String(s) => s.to_object(py),
Value::String(s) => s.into_bound_py_any(py),
Value::Array(v) => {
let inner: Vec<_> = v.iter().map(|x| value_to_object(py, x)).collect();
inner.to_object(py)
let list = PyList::empty(py);
for item in v {
list.append(value_to_object(py, item)?)?;
}
list.into_bound_py_any(py)
}
Value::Object(m) => {
let inner: HashMap<_, _> = m.iter().map(|(k, v)| (k, value_to_object(py, v))).collect();
inner.to_object(py)
let dict = PyDict::new(py);
for (key, value) in m {
dict.set_item(key, value_to_object(py, value)?)?;
}
dict.into_bound_py_any(py)
}
}
}
impl ToPyObject for PyValue {
fn to_object(&self, py: Python<'_>) -> PyObject {
impl<'py> IntoPyObject<'py> for PyValue {
type Target = PyAny;
type Output = Bound<'py, PyAny>;
type Error = PyErr;
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
value_to_object(py, &self.0)
}
}