dirstate_map.rs
528 lines
| 16.1 KiB
| application/rls-services+xml
|
RustLexer
Raphaël Gomès
|
r42999 | // dirstate_map.rs | ||
// | ||||
// Copyright 2019 Raphaël Gomès <rgomes@octobus.net> | ||||
// | ||||
// This software may be used and distributed according to the terms of the | ||||
// GNU General Public License version 2 or any later version. | ||||
//! Bindings for the `hg::dirstate::dirstate_map` file provided by the | ||||
//! `hg-core` package. | ||||
use std::cell::RefCell; | ||||
use std::convert::TryInto; | ||||
use std::time::Duration; | ||||
use cpython::{ | ||||
exc, ObjectProtocol, PyBool, PyBytes, PyClone, PyDict, PyErr, PyObject, | ||||
PyResult, PyTuple, Python, PythonObject, ToPyObject, | ||||
}; | ||||
use libc::c_char; | ||||
use crate::{ | ||||
dirstate::copymap::{CopyMap, CopyMapItemsIterator, CopyMapKeysIterator}, | ||||
dirstate::{decapsule_make_dirstate_tuple, dirs_multiset::Dirs}, | ||||
Yuya Nishihara
|
r43447 | ref_sharing::{PyLeakedRef, PySharedRefCell}, | ||
Raphaël Gomès
|
r42999 | }; | ||
use hg::{ | ||||
Raphaël Gomès
|
r43227 | utils::hg_path::{HgPath, HgPathBuf}, | ||
Yuya Nishihara
|
r43070 | DirsMultiset, DirstateEntry, DirstateMap as RustDirstateMap, | ||
Yuya Nishihara
|
r43158 | DirstateParents, DirstateParseError, EntryState, StateMapIter, | ||
PARENT_SIZE, | ||||
Raphaël Gomès
|
r42999 | }; | ||
// TODO | ||||
// This object needs to share references to multiple members of its Rust | ||||
// inner struct, namely `copy_map`, `dirs` and `all_dirs`. | ||||
// Right now `CopyMap` is done, but it needs to have an explicit reference | ||||
// to `RustDirstateMap` which itself needs to have an encapsulation for | ||||
// every method in `CopyMap` (copymapcopy, etc.). | ||||
// This is ugly and hard to maintain. | ||||
// The same logic applies to `dirs` and `all_dirs`, however the `Dirs` | ||||
// `py_class!` is already implemented and does not mention | ||||
// `RustDirstateMap`, rightfully so. | ||||
// All attributes also have to have a separate refcount data attribute for | ||||
// leaks, with all methods that go along for reference sharing. | ||||
py_class!(pub class DirstateMap |py| { | ||||
Yuya Nishihara
|
r43115 | data inner: PySharedRefCell<RustDirstateMap>; | ||
Raphaël Gomès
|
r42999 | |||
def __new__(_cls, _root: PyObject) -> PyResult<Self> { | ||||
let inner = RustDirstateMap::default(); | ||||
Self::create_instance( | ||||
py, | ||||
Yuya Nishihara
|
r43115 | PySharedRefCell::new(inner), | ||
Raphaël Gomès
|
r42999 | ) | ||
} | ||||
def clear(&self) -> PyResult<PyObject> { | ||||
self.borrow_mut(py)?.clear(); | ||||
Ok(py.None()) | ||||
} | ||||
def get( | ||||
&self, | ||||
key: PyObject, | ||||
default: Option<PyObject> = None | ||||
) -> PyResult<Option<PyObject>> { | ||||
let key = key.extract::<PyBytes>(py)?; | ||||
Raphaël Gomès
|
r43227 | match self.inner(py).borrow().get(HgPath::new(key.data(py))) { | ||
Raphaël Gomès
|
r42999 | Some(entry) => { | ||
// Explicitly go through u8 first, then cast to | ||||
// platform-specific `c_char`. | ||||
let state: u8 = entry.state.into(); | ||||
Ok(Some(decapsule_make_dirstate_tuple(py)?( | ||||
state as c_char, | ||||
entry.mode, | ||||
entry.size, | ||||
entry.mtime, | ||||
))) | ||||
}, | ||||
None => Ok(default) | ||||
} | ||||
} | ||||
def addfile( | ||||
&self, | ||||
f: PyObject, | ||||
oldstate: PyObject, | ||||
state: PyObject, | ||||
mode: PyObject, | ||||
size: PyObject, | ||||
mtime: PyObject | ||||
) -> PyResult<PyObject> { | ||||
self.borrow_mut(py)?.add_file( | ||||
Raphaël Gomès
|
r43227 | HgPath::new(f.extract::<PyBytes>(py)?.data(py)), | ||
Raphaël Gomès
|
r42999 | oldstate.extract::<PyBytes>(py)?.data(py)[0] | ||
.try_into() | ||||
.map_err(|e: DirstateParseError| { | ||||
PyErr::new::<exc::ValueError, _>(py, e.to_string()) | ||||
})?, | ||||
DirstateEntry { | ||||
state: state.extract::<PyBytes>(py)?.data(py)[0] | ||||
.try_into() | ||||
.map_err(|e: DirstateParseError| { | ||||
PyErr::new::<exc::ValueError, _>(py, e.to_string()) | ||||
})?, | ||||
mode: mode.extract(py)?, | ||||
size: size.extract(py)?, | ||||
mtime: mtime.extract(py)?, | ||||
}, | ||||
); | ||||
Ok(py.None()) | ||||
} | ||||
def removefile( | ||||
&self, | ||||
f: PyObject, | ||||
oldstate: PyObject, | ||||
size: PyObject | ||||
) -> PyResult<PyObject> { | ||||
self.borrow_mut(py)? | ||||
.remove_file( | ||||
Raphaël Gomès
|
r43227 | HgPath::new(f.extract::<PyBytes>(py)?.data(py)), | ||
Raphaël Gomès
|
r42999 | oldstate.extract::<PyBytes>(py)?.data(py)[0] | ||
.try_into() | ||||
.map_err(|e: DirstateParseError| { | ||||
PyErr::new::<exc::ValueError, _>(py, e.to_string()) | ||||
})?, | ||||
size.extract(py)?, | ||||
) | ||||
.or_else(|_| { | ||||
Err(PyErr::new::<exc::OSError, _>( | ||||
py, | ||||
"Dirstate error".to_string(), | ||||
)) | ||||
})?; | ||||
Ok(py.None()) | ||||
} | ||||
def dropfile( | ||||
&self, | ||||
f: PyObject, | ||||
oldstate: PyObject | ||||
) -> PyResult<PyBool> { | ||||
self.borrow_mut(py)? | ||||
.drop_file( | ||||
Raphaël Gomès
|
r43227 | HgPath::new(f.extract::<PyBytes>(py)?.data(py)), | ||
Raphaël Gomès
|
r42999 | oldstate.extract::<PyBytes>(py)?.data(py)[0] | ||
.try_into() | ||||
.map_err(|e: DirstateParseError| { | ||||
PyErr::new::<exc::ValueError, _>(py, e.to_string()) | ||||
})?, | ||||
) | ||||
.and_then(|b| Ok(b.to_py_object(py))) | ||||
.or_else(|_| { | ||||
Err(PyErr::new::<exc::OSError, _>( | ||||
py, | ||||
"Dirstate error".to_string(), | ||||
)) | ||||
}) | ||||
} | ||||
def clearambiguoustimes( | ||||
&self, | ||||
files: PyObject, | ||||
now: PyObject | ||||
) -> PyResult<PyObject> { | ||||
Raphaël Gomès
|
r43227 | let files: PyResult<Vec<HgPathBuf>> = files | ||
Raphaël Gomès
|
r42999 | .iter(py)? | ||
.map(|filename| { | ||||
Raphaël Gomès
|
r43227 | Ok(HgPathBuf::from_bytes( | ||
filename?.extract::<PyBytes>(py)?.data(py), | ||||
)) | ||||
Raphaël Gomès
|
r42999 | }) | ||
.collect(); | ||||
Yuya Nishihara
|
r43114 | self.borrow_mut(py)? | ||
Raphaël Gomès
|
r42999 | .clear_ambiguous_times(files?, now.extract(py)?); | ||
Ok(py.None()) | ||||
} | ||||
// TODO share the reference | ||||
def nonnormalentries(&self) -> PyResult<PyObject> { | ||||
let (non_normal, other_parent) = | ||||
self.inner(py).borrow().non_normal_other_parent_entries(); | ||||
let locals = PyDict::new(py); | ||||
locals.set_item( | ||||
py, | ||||
"non_normal", | ||||
non_normal | ||||
.iter() | ||||
Raphaël Gomès
|
r43227 | .map(|v| PyBytes::new(py, v.as_ref())) | ||
Raphaël Gomès
|
r42999 | .collect::<Vec<PyBytes>>() | ||
.to_py_object(py), | ||||
)?; | ||||
locals.set_item( | ||||
py, | ||||
"other_parent", | ||||
other_parent | ||||
.iter() | ||||
Raphaël Gomès
|
r43227 | .map(|v| PyBytes::new(py, v.as_ref())) | ||
Raphaël Gomès
|
r42999 | .collect::<Vec<PyBytes>>() | ||
.to_py_object(py), | ||||
)?; | ||||
py.eval("set(non_normal), set(other_parent)", None, Some(&locals)) | ||||
} | ||||
def hastrackeddir(&self, d: PyObject) -> PyResult<PyBool> { | ||||
let d = d.extract::<PyBytes>(py)?; | ||||
Yuya Nishihara
|
r43114 | Ok(self.borrow_mut(py)? | ||
Raphaël Gomès
|
r43227 | .has_tracked_dir(HgPath::new(d.data(py))) | ||
Raphaël Gomès
|
r42999 | .to_py_object(py)) | ||
} | ||||
def hasdir(&self, d: PyObject) -> PyResult<PyBool> { | ||||
let d = d.extract::<PyBytes>(py)?; | ||||
Yuya Nishihara
|
r43114 | Ok(self.borrow_mut(py)? | ||
Raphaël Gomès
|
r43227 | .has_dir(HgPath::new(d.data(py))) | ||
Raphaël Gomès
|
r42999 | .to_py_object(py)) | ||
} | ||||
def parents(&self, st: PyObject) -> PyResult<PyTuple> { | ||||
Yuya Nishihara
|
r43114 | self.borrow_mut(py)? | ||
Raphaël Gomès
|
r42999 | .parents(st.extract::<PyBytes>(py)?.data(py)) | ||
.and_then(|d| { | ||||
Ok((PyBytes::new(py, &d.p1), PyBytes::new(py, &d.p2)) | ||||
.to_py_object(py)) | ||||
}) | ||||
.or_else(|_| { | ||||
Err(PyErr::new::<exc::OSError, _>( | ||||
py, | ||||
"Dirstate error".to_string(), | ||||
)) | ||||
}) | ||||
} | ||||
def setparents(&self, p1: PyObject, p2: PyObject) -> PyResult<PyObject> { | ||||
Yuya Nishihara
|
r43068 | let p1 = extract_node_id(py, &p1)?; | ||
let p2 = extract_node_id(py, &p2)?; | ||||
Raphaël Gomès
|
r42999 | |||
Yuya Nishihara
|
r43114 | self.borrow_mut(py)? | ||
Yuya Nishihara
|
r43069 | .set_parents(&DirstateParents { p1, p2 }); | ||
Raphaël Gomès
|
r42999 | Ok(py.None()) | ||
} | ||||
def read(&self, st: PyObject) -> PyResult<Option<PyObject>> { | ||||
Yuya Nishihara
|
r43114 | match self.borrow_mut(py)? | ||
Raphaël Gomès
|
r42999 | .read(st.extract::<PyBytes>(py)?.data(py)) | ||
{ | ||||
Ok(Some(parents)) => Ok(Some( | ||||
(PyBytes::new(py, &parents.p1), PyBytes::new(py, &parents.p2)) | ||||
.to_py_object(py) | ||||
.into_object(), | ||||
)), | ||||
Ok(None) => Ok(Some(py.None())), | ||||
Err(_) => Err(PyErr::new::<exc::OSError, _>( | ||||
py, | ||||
"Dirstate error".to_string(), | ||||
)), | ||||
} | ||||
} | ||||
def write( | ||||
&self, | ||||
p1: PyObject, | ||||
p2: PyObject, | ||||
now: PyObject | ||||
) -> PyResult<PyBytes> { | ||||
let now = Duration::new(now.extract(py)?, 0); | ||||
let parents = DirstateParents { | ||||
Yuya Nishihara
|
r43068 | p1: extract_node_id(py, &p1)?, | ||
p2: extract_node_id(py, &p2)?, | ||||
Raphaël Gomès
|
r42999 | }; | ||
match self.borrow_mut(py)?.pack(parents, now) { | ||||
Ok(packed) => Ok(PyBytes::new(py, &packed)), | ||||
Err(_) => Err(PyErr::new::<exc::OSError, _>( | ||||
py, | ||||
"Dirstate error".to_string(), | ||||
)), | ||||
} | ||||
} | ||||
def filefoldmapasdict(&self) -> PyResult<PyDict> { | ||||
let dict = PyDict::new(py); | ||||
Raphaël Gomès
|
r43227 | for (key, value) in self.borrow_mut(py)?.build_file_fold_map().iter() { | ||
dict.set_item(py, key.as_ref().to_vec(), value.as_ref().to_vec())?; | ||||
Raphaël Gomès
|
r42999 | } | ||
Ok(dict) | ||||
} | ||||
def __len__(&self) -> PyResult<usize> { | ||||
Ok(self.inner(py).borrow().len()) | ||||
} | ||||
def __contains__(&self, key: PyObject) -> PyResult<bool> { | ||||
let key = key.extract::<PyBytes>(py)?; | ||||
Raphaël Gomès
|
r43227 | Ok(self.inner(py).borrow().contains_key(HgPath::new(key.data(py)))) | ||
Raphaël Gomès
|
r42999 | } | ||
def __getitem__(&self, key: PyObject) -> PyResult<PyObject> { | ||||
let key = key.extract::<PyBytes>(py)?; | ||||
Raphaël Gomès
|
r43227 | let key = HgPath::new(key.data(py)); | ||
Raphaël Gomès
|
r42999 | match self.inner(py).borrow().get(key) { | ||
Some(entry) => { | ||||
// Explicitly go through u8 first, then cast to | ||||
// platform-specific `c_char`. | ||||
let state: u8 = entry.state.into(); | ||||
Ok(decapsule_make_dirstate_tuple(py)?( | ||||
state as c_char, | ||||
entry.mode, | ||||
entry.size, | ||||
entry.mtime, | ||||
)) | ||||
}, | ||||
None => Err(PyErr::new::<exc::KeyError, _>( | ||||
py, | ||||
Raphaël Gomès
|
r43227 | String::from_utf8_lossy(key.as_bytes()), | ||
Raphaël Gomès
|
r42999 | )), | ||
} | ||||
} | ||||
def keys(&self) -> PyResult<DirstateMapKeysIterator> { | ||||
Yuya Nishihara
|
r43117 | let (leak_handle, leaked_ref) = unsafe { self.leak_immutable(py)? }; | ||
Raphaël Gomès
|
r42999 | DirstateMapKeysIterator::from_inner( | ||
py, | ||||
Yuya Nishihara
|
r43160 | leak_handle, | ||
Yuya Nishihara
|
r43158 | leaked_ref.iter(), | ||
Raphaël Gomès
|
r42999 | ) | ||
} | ||||
def items(&self) -> PyResult<DirstateMapItemsIterator> { | ||||
Yuya Nishihara
|
r43117 | let (leak_handle, leaked_ref) = unsafe { self.leak_immutable(py)? }; | ||
Raphaël Gomès
|
r42999 | DirstateMapItemsIterator::from_inner( | ||
py, | ||||
Yuya Nishihara
|
r43160 | leak_handle, | ||
Yuya Nishihara
|
r43158 | leaked_ref.iter(), | ||
Raphaël Gomès
|
r42999 | ) | ||
} | ||||
def __iter__(&self) -> PyResult<DirstateMapKeysIterator> { | ||||
Yuya Nishihara
|
r43117 | let (leak_handle, leaked_ref) = unsafe { self.leak_immutable(py)? }; | ||
Raphaël Gomès
|
r42999 | DirstateMapKeysIterator::from_inner( | ||
py, | ||||
Yuya Nishihara
|
r43160 | leak_handle, | ||
Yuya Nishihara
|
r43158 | leaked_ref.iter(), | ||
Raphaël Gomès
|
r42999 | ) | ||
} | ||||
def getdirs(&self) -> PyResult<Dirs> { | ||||
// TODO don't copy, share the reference | ||||
Yuya Nishihara
|
r43114 | self.borrow_mut(py)?.set_dirs(); | ||
Raphaël Gomès
|
r42999 | Dirs::from_inner( | ||
py, | ||||
Yuya Nishihara
|
r43070 | DirsMultiset::from_dirstate( | ||
&self.inner(py).borrow(), | ||||
Raphaël Gomès
|
r42999 | Some(EntryState::Removed), | ||
), | ||||
) | ||||
} | ||||
def getalldirs(&self) -> PyResult<Dirs> { | ||||
// TODO don't copy, share the reference | ||||
Yuya Nishihara
|
r43114 | self.borrow_mut(py)?.set_all_dirs(); | ||
Raphaël Gomès
|
r42999 | Dirs::from_inner( | ||
py, | ||||
Yuya Nishihara
|
r43070 | DirsMultiset::from_dirstate( | ||
&self.inner(py).borrow(), | ||||
Raphaël Gomès
|
r42999 | None, | ||
), | ||||
) | ||||
} | ||||
// TODO all copymap* methods, see docstring above | ||||
def copymapcopy(&self) -> PyResult<PyDict> { | ||||
let dict = PyDict::new(py); | ||||
for (key, value) in self.inner(py).borrow().copy_map.iter() { | ||||
Raphaël Gomès
|
r43227 | dict.set_item( | ||
py, | ||||
PyBytes::new(py, key.as_ref()), | ||||
PyBytes::new(py, value.as_ref()), | ||||
)?; | ||||
Raphaël Gomès
|
r42999 | } | ||
Ok(dict) | ||||
} | ||||
def copymapgetitem(&self, key: PyObject) -> PyResult<PyBytes> { | ||||
let key = key.extract::<PyBytes>(py)?; | ||||
Raphaël Gomès
|
r43227 | match self.inner(py).borrow().copy_map.get(HgPath::new(key.data(py))) { | ||
Some(copy) => Ok(PyBytes::new(py, copy.as_ref())), | ||||
Raphaël Gomès
|
r42999 | None => Err(PyErr::new::<exc::KeyError, _>( | ||
py, | ||||
String::from_utf8_lossy(key.data(py)), | ||||
)), | ||||
} | ||||
} | ||||
def copymap(&self) -> PyResult<CopyMap> { | ||||
CopyMap::from_inner(py, self.clone_ref(py)) | ||||
} | ||||
def copymaplen(&self) -> PyResult<usize> { | ||||
Ok(self.inner(py).borrow().copy_map.len()) | ||||
} | ||||
def copymapcontains(&self, key: PyObject) -> PyResult<bool> { | ||||
let key = key.extract::<PyBytes>(py)?; | ||||
Raphaël Gomès
|
r43227 | Ok(self | ||
.inner(py) | ||||
.borrow() | ||||
.copy_map | ||||
.contains_key(HgPath::new(key.data(py)))) | ||||
Raphaël Gomès
|
r42999 | } | ||
def copymapget( | ||||
&self, | ||||
key: PyObject, | ||||
default: Option<PyObject> | ||||
) -> PyResult<Option<PyObject>> { | ||||
let key = key.extract::<PyBytes>(py)?; | ||||
Raphaël Gomès
|
r43227 | match self | ||
.inner(py) | ||||
.borrow() | ||||
.copy_map | ||||
.get(HgPath::new(key.data(py))) | ||||
{ | ||||
Some(copy) => Ok(Some( | ||||
PyBytes::new(py, copy.as_ref()).into_object(), | ||||
)), | ||||
Raphaël Gomès
|
r42999 | None => Ok(default), | ||
} | ||||
} | ||||
def copymapsetitem( | ||||
&self, | ||||
key: PyObject, | ||||
value: PyObject | ||||
) -> PyResult<PyObject> { | ||||
let key = key.extract::<PyBytes>(py)?; | ||||
let value = value.extract::<PyBytes>(py)?; | ||||
Raphaël Gomès
|
r43227 | self.borrow_mut(py)?.copy_map.insert( | ||
HgPathBuf::from_bytes(key.data(py)), | ||||
HgPathBuf::from_bytes(value.data(py)), | ||||
); | ||||
Raphaël Gomès
|
r42999 | Ok(py.None()) | ||
} | ||||
def copymappop( | ||||
&self, | ||||
key: PyObject, | ||||
default: Option<PyObject> | ||||
) -> PyResult<Option<PyObject>> { | ||||
let key = key.extract::<PyBytes>(py)?; | ||||
Raphaël Gomès
|
r43227 | match self | ||
.borrow_mut(py)? | ||||
.copy_map | ||||
.remove(HgPath::new(key.data(py))) | ||||
{ | ||||
Raphaël Gomès
|
r42999 | Some(_) => Ok(None), | ||
None => Ok(default), | ||||
} | ||||
} | ||||
def copymapiter(&self) -> PyResult<CopyMapKeysIterator> { | ||||
Yuya Nishihara
|
r43117 | let (leak_handle, leaked_ref) = unsafe { self.leak_immutable(py)? }; | ||
Raphaël Gomès
|
r42999 | CopyMapKeysIterator::from_inner( | ||
py, | ||||
Yuya Nishihara
|
r43160 | leak_handle, | ||
Yuya Nishihara
|
r43158 | leaked_ref.copy_map.iter(), | ||
Raphaël Gomès
|
r42999 | ) | ||
} | ||||
def copymapitemsiter(&self) -> PyResult<CopyMapItemsIterator> { | ||||
Yuya Nishihara
|
r43117 | let (leak_handle, leaked_ref) = unsafe { self.leak_immutable(py)? }; | ||
Raphaël Gomès
|
r42999 | CopyMapItemsIterator::from_inner( | ||
py, | ||||
Yuya Nishihara
|
r43160 | leak_handle, | ||
Yuya Nishihara
|
r43158 | leaked_ref.copy_map.iter(), | ||
Raphaël Gomès
|
r42999 | ) | ||
} | ||||
}); | ||||
impl DirstateMap { | ||||
fn translate_key( | ||||
py: Python, | ||||
Raphaël Gomès
|
r43227 | res: (&HgPathBuf, &DirstateEntry), | ||
Raphaël Gomès
|
r42999 | ) -> PyResult<Option<PyBytes>> { | ||
Raphaël Gomès
|
r43227 | Ok(Some(PyBytes::new(py, res.0.as_ref()))) | ||
Raphaël Gomès
|
r42999 | } | ||
fn translate_key_value( | ||||
py: Python, | ||||
Raphaël Gomès
|
r43227 | res: (&HgPathBuf, &DirstateEntry), | ||
Raphaël Gomès
|
r42999 | ) -> PyResult<Option<(PyBytes, PyObject)>> { | ||
let (f, entry) = res; | ||||
// Explicitly go through u8 first, then cast to | ||||
// platform-specific `c_char`. | ||||
let state: u8 = entry.state.into(); | ||||
Ok(Some(( | ||||
Raphaël Gomès
|
r43227 | PyBytes::new(py, f.as_ref()), | ||
Raphaël Gomès
|
r42999 | decapsule_make_dirstate_tuple(py)?( | ||
state as c_char, | ||||
entry.mode, | ||||
entry.size, | ||||
entry.mtime, | ||||
), | ||||
))) | ||||
} | ||||
} | ||||
Yuya Nishihara
|
r43447 | py_shared_ref!(DirstateMap, RustDirstateMap, inner); | ||
Raphaël Gomès
|
r42999 | |||
Yuya Nishihara
|
r43159 | py_shared_iterator!( | ||
Raphaël Gomès
|
r42999 | DirstateMapKeysIterator, | ||
Yuya Nishihara
|
r43447 | PyLeakedRef, | ||
Yuya Nishihara
|
r43158 | StateMapIter<'static>, | ||
Raphaël Gomès
|
r42999 | DirstateMap::translate_key, | ||
Option<PyBytes> | ||||
); | ||||
Yuya Nishihara
|
r43159 | py_shared_iterator!( | ||
Raphaël Gomès
|
r42999 | DirstateMapItemsIterator, | ||
Yuya Nishihara
|
r43447 | PyLeakedRef, | ||
Yuya Nishihara
|
r43158 | StateMapIter<'static>, | ||
Raphaël Gomès
|
r42999 | DirstateMap::translate_key_value, | ||
Option<(PyBytes, PyObject)> | ||||
); | ||||
Yuya Nishihara
|
r43068 | |||
fn extract_node_id(py: Python, obj: &PyObject) -> PyResult<[u8; PARENT_SIZE]> { | ||||
let bytes = obj.extract::<PyBytes>(py)?; | ||||
match bytes.data(py).try_into() { | ||||
Ok(s) => Ok(s), | ||||
Err(e) => Err(PyErr::new::<exc::ValueError, _>(py, e.to_string())), | ||||
} | ||||
} | ||||