microsoft/qdk
Publicmirrored fromhttps://github.com/microsoft/qdkAvailable
source/pip/src/generic_estimator/utils.rs
164lines · modecode
| 1 | // Copyright (c) Microsoft Corporation. |
| 2 | // Licensed under the MIT License. |
| 3 | |
| 4 | use std::ops::Deref; |
| 5 | |
| 6 | use pyo3::{ |
| 7 | Bound, IntoPyObject, PyAny, PyErr, PyResult, Python, |
| 8 | exceptions::PyTypeError, |
| 9 | types::{ |
| 10 | PyAnyMethods, PyBool, PyDict, PyDictMethods, PyFloat, PyInt, PyList, PyListMethods, PyNone, |
| 11 | PyString, PyTypeMethods, |
| 12 | }, |
| 13 | }; |
| 14 | use serde::{Serialize, ser::SerializeMap}; |
| 15 | use serde_json::{Map, Value, json}; |
| 16 | |
| 17 | /// Converts a JSON value to a Python object, handling various types such as |
| 18 | /// `null`, `number`, `string`, `boolean`, `array`, and `object`. |
| 19 | fn json_value_to_python_object<'py>(py: Python<'py>, value: &Value) -> PyResult<Bound<'py, PyAny>> { |
| 20 | match value { |
| 21 | Value::Null => Ok(PyNone::get(py).to_owned().into_any()), |
| 22 | Value::Number(n) => { |
| 23 | if let Some(int) = n.as_i64() { |
| 24 | Ok(int.into_pyobject(py)?.into_any()) |
| 25 | } else if let Some(float) = n.as_f64() { |
| 26 | Ok(float.into_pyobject(py)?.into_any()) |
| 27 | } else { |
| 28 | Err(PyErr::new::<PyTypeError, _>(format!("cannot convert {n}"))) |
| 29 | } |
| 30 | } |
| 31 | Value::String(s) => Ok(PyString::new(py, s).into_any()), |
| 32 | &Value::Bool(b) => Ok(b.into_pyobject(py)?.to_owned().into_any()), |
| 33 | Value::Array(elements) => { |
| 34 | let list = PyList::empty(py); |
| 35 | |
| 36 | for element in elements { |
| 37 | list.append(json_value_to_python_object(py, element)?)?; |
| 38 | } |
| 39 | |
| 40 | Ok(list.into_any()) |
| 41 | } |
| 42 | Value::Object(map) => Ok(json_map_to_python_dict(py, map)?.into_any()), |
| 43 | } |
| 44 | } |
| 45 | |
| 46 | /// Converts a JSON map to a Python dictionary. |
| 47 | pub(crate) fn json_map_to_python_dict<'py>( |
| 48 | py: Python<'py>, |
| 49 | map: &Map<String, Value>, |
| 50 | ) -> PyResult<Bound<'py, PyDict>> { |
| 51 | let dict = PyDict::new(py); |
| 52 | for (key, value) in map { |
| 53 | dict.set_item(key, json_value_to_python_object(py, value)?)?; |
| 54 | } |
| 55 | Ok(dict) |
| 56 | } |
| 57 | |
| 58 | /// Converts a Python object to a JSON value, handling various types such as |
| 59 | /// `None`, `int`, `float`, `bool`, `str`, `list`, and `dict`. |
| 60 | pub(crate) fn python_object_to_json_value(value: &Bound<'_, PyAny>) -> Option<Value> { |
| 61 | if value.is_none() { |
| 62 | Some(Value::Null) |
| 63 | } else if let Ok(n) = value.downcast_exact::<PyInt>() { |
| 64 | Some(json!(n.extract::<i64>().expect("n is PyInt"))) |
| 65 | } else if let Ok(n) = value.downcast_exact::<PyFloat>() { |
| 66 | Some(json!(n.extract::<f64>().expect("n is PyFloat"))) |
| 67 | } else if let Ok(b) = value.downcast_exact::<PyBool>() { |
| 68 | Some(json!(b.extract::<bool>().expect("b is PyBool"))) |
| 69 | } else if let Ok(s) = value.downcast_exact::<PyString>() { |
| 70 | Some(json!(s.extract::<String>().expect("s is PyString"))) |
| 71 | } else if let Ok(l) = value.downcast_exact::<PyList>() { |
| 72 | let values: Vec<_> = l |
| 73 | .iter() |
| 74 | .map(|v| python_object_to_json_value(&v)) |
| 75 | .collect::<Option<_>>()?; |
| 76 | Some(Value::Array(values)) |
| 77 | } else if let Ok(d) = value.downcast_exact::<PyDict>() { |
| 78 | Some(Value::Object(python_dict_to_json_map(d)?)) |
| 79 | } else { |
| 80 | None |
| 81 | } |
| 82 | } |
| 83 | |
| 84 | /// Converts a Python dictionary to a JSON map. |
| 85 | pub fn python_dict_to_json_map(value: &Bound<'_, PyDict>) -> Option<Map<String, Value>> { |
| 86 | let mut map: Map<String, Value> = Map::new(); |
| 87 | for (key, value) in value.iter() { |
| 88 | map.insert( |
| 89 | key.extract::<String>().ok()?, |
| 90 | python_object_to_json_value(&value)?, |
| 91 | ); |
| 92 | } |
| 93 | Some(map) |
| 94 | } |
| 95 | |
| 96 | /// A wrapper around a Python instance that can be serialized in order to embed |
| 97 | /// its value into the returned resource estimates. |
| 98 | #[derive(Clone, Debug)] |
| 99 | pub struct SerializableBound<'py>(pub Bound<'py, PyAny>); |
| 100 | |
| 101 | impl<'py> Deref for SerializableBound<'py> { |
| 102 | type Target = Bound<'py, PyAny>; |
| 103 | |
| 104 | fn deref(&self) -> &Self::Target { |
| 105 | &self.0 |
| 106 | } |
| 107 | } |
| 108 | |
| 109 | impl Serialize for SerializableBound<'_> { |
| 110 | fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> |
| 111 | where |
| 112 | S: serde::Serializer, |
| 113 | { |
| 114 | if let Ok(dict) = self.downcast::<PyDict>() { |
| 115 | let mut map = serializer.serialize_map(Some(dict.len()))?; |
| 116 | for (key, value) in dict.iter() { |
| 117 | map.serialize_key(&SerializableBound(key))?; |
| 118 | map.serialize_value(&SerializableBound(value))?; |
| 119 | } |
| 120 | map.end() |
| 121 | } else if let Ok(number) = self.downcast::<PyInt>() { |
| 122 | serializer.serialize_i64(number.extract().expect("number is PyInt")) |
| 123 | } else { |
| 124 | serializer.serialize_str(&self.to_string()) |
| 125 | } |
| 126 | } |
| 127 | } |
| 128 | |
| 129 | /// Extracts a method from a Python instance and checks if it is callable |
| 130 | pub fn extract_and_check_method<'py>( |
| 131 | instance: &Bound<'py, PyAny>, |
| 132 | method_name: &str, |
| 133 | ) -> PyResult<Bound<'py, PyAny>> { |
| 134 | let member = instance.getattr(method_name)?; |
| 135 | if !member.is_callable() { |
| 136 | return Err(PyTypeError::new_err(format!( |
| 137 | "Method '{}' is not callable on the instance of type '{}'", |
| 138 | method_name, |
| 139 | instance.get_type().name()? |
| 140 | ))); |
| 141 | } |
| 142 | Ok(member) |
| 143 | } |
| 144 | |
| 145 | /// Attempts to extract a method from a Python instance and checks if it is |
| 146 | /// callable |
| 147 | pub fn maybe_extract_and_check_method<'py>( |
| 148 | instance: &Bound<'py, PyAny>, |
| 149 | method_name: &str, |
| 150 | ) -> PyResult<Option<Bound<'py, PyAny>>> { |
| 151 | if !instance.hasattr(method_name)? { |
| 152 | return Ok(None); |
| 153 | } |
| 154 | |
| 155 | let member = instance.getattr(method_name)?; |
| 156 | if !member.is_callable() { |
| 157 | return Err(PyTypeError::new_err(format!( |
| 158 | "Method '{}' is not callable on the instance of type '{}'", |
| 159 | method_name, |
| 160 | instance.get_type().name()? |
| 161 | ))); |
| 162 | } |
| 163 | Ok(Some(member)) |
| 164 | } |
| 165 | |