|
1 |
| -use std::borrow::Cow; |
2 | 1 | use std::str::from_utf8;
|
3 | 2 |
|
4 | 3 | use pyo3::intern;
|
@@ -145,12 +144,8 @@ impl<'py> Input<'py> for Bound<'py, PyAny> {
|
145 | 144 | Err(_) => Err(ValError::new(ErrorTypeDefaults::StringUnicode, self)),
|
146 | 145 | }
|
147 | 146 | } else if let Ok(py_byte_array) = self.downcast::<PyByteArray>() {
|
148 |
| - // Safety: the gil is held while from_utf8 is running so py_byte_array is not mutated, |
149 |
| - // and we immediately copy the bytes into a new Python string |
150 |
| - match from_utf8(unsafe { py_byte_array.as_bytes() }) { |
151 |
| - // Why Python not Rust? to avoid an unnecessary allocation on the Rust side, the |
152 |
| - // final output needs to be Python anyway. |
153 |
| - Ok(s) => Ok(PyString::new_bound(self.py(), s).into()), |
| 147 | + match bytearray_to_str(py_byte_array) { |
| 148 | + Ok(py_str) => Ok(py_str.into()), |
154 | 149 | Err(_) => Err(ValError::new(ErrorTypeDefaults::StringUnicode, self)),
|
155 | 150 | }
|
156 | 151 | } else if coerce_numbers_to_str && !self.is_exact_instance_of::<PyBool>() && {
|
@@ -212,8 +207,8 @@ impl<'py> Input<'py> for Bound<'py, PyAny> {
|
212 | 207 | }
|
213 | 208 |
|
214 | 209 | if !strict {
|
215 |
| - if let Some(cow_str) = maybe_as_string(self, ErrorTypeDefaults::BoolParsing)? { |
216 |
| - return str_as_bool(self, &cow_str).map(ValidationMatch::lax); |
| 210 | + if let Some(s) = maybe_as_string(self, ErrorTypeDefaults::BoolParsing)? { |
| 211 | + return str_as_bool(self, s).map(ValidationMatch::lax); |
217 | 212 | } else if let Some(int) = extract_i64(self) {
|
218 | 213 | return int_as_bool(self, int).map(ValidationMatch::lax);
|
219 | 214 | } else if let Ok(float) = self.extract::<f64>() {
|
@@ -249,8 +244,8 @@ impl<'py> Input<'py> for Bound<'py, PyAny> {
|
249 | 244 |
|
250 | 245 | 'lax: {
|
251 | 246 | if !strict {
|
252 |
| - return if let Some(cow_str) = maybe_as_string(self, ErrorTypeDefaults::IntParsing)? { |
253 |
| - str_as_int(self, &cow_str) |
| 247 | + return if let Some(s) = maybe_as_string(self, ErrorTypeDefaults::IntParsing)? { |
| 248 | + str_as_int(self, s) |
254 | 249 | } else if self.is_exact_instance_of::<PyFloat>() {
|
255 | 250 | float_as_int(self, self.extract::<f64>()?)
|
256 | 251 | } else if let Ok(decimal) = self.strict_decimal(self.py()) {
|
@@ -291,9 +286,9 @@ impl<'py> Input<'py> for Bound<'py, PyAny> {
|
291 | 286 | }
|
292 | 287 |
|
293 | 288 | if !strict {
|
294 |
| - if let Some(cow_str) = maybe_as_string(self, ErrorTypeDefaults::FloatParsing)? { |
| 289 | + if let Some(s) = maybe_as_string(self, ErrorTypeDefaults::FloatParsing)? { |
295 | 290 | // checking for bytes and string is fast, so do this before isinstance(float)
|
296 |
| - return str_as_float(self, &cow_str).map(ValidationMatch::lax); |
| 291 | + return str_as_float(self, s).map(ValidationMatch::lax); |
297 | 292 | }
|
298 | 293 | }
|
299 | 294 |
|
@@ -638,20 +633,31 @@ fn from_attributes_applicable(obj: &Bound<'_, PyAny>) -> bool {
|
638 | 633 | }
|
639 | 634 |
|
640 | 635 | /// Utility for extracting a string from a PyAny, if possible.
|
641 |
| -fn maybe_as_string<'a>(v: &'a Bound<'_, PyAny>, unicode_error: ErrorType) -> ValResult<Option<Cow<'a, str>>> { |
| 636 | +fn maybe_as_string<'a>(v: &'a Bound<'_, PyAny>, unicode_error: ErrorType) -> ValResult<Option<&'a str>> { |
642 | 637 | if let Ok(py_string) = v.downcast::<PyString>() {
|
643 |
| - let str = py_string_str(py_string)?; |
644 |
| - Ok(Some(Cow::Borrowed(str))) |
| 638 | + py_string_str(py_string).map(Some) |
645 | 639 | } else if let Ok(bytes) = v.downcast::<PyBytes>() {
|
646 | 640 | match from_utf8(bytes.as_bytes()) {
|
647 |
| - Ok(s) => Ok(Some(Cow::Owned(s.to_string()))), |
| 641 | + Ok(s) => Ok(Some(s)), |
648 | 642 | Err(_) => Err(ValError::new(unicode_error, v)),
|
649 | 643 | }
|
650 | 644 | } else {
|
651 | 645 | Ok(None)
|
652 | 646 | }
|
653 | 647 | }
|
654 | 648 |
|
| 649 | +/// Decode a Python bytearray to a Python string. |
| 650 | +/// |
| 651 | +/// Using Python's built-in machinery for this should be efficient and avoids questions around |
| 652 | +/// safety of concurrent mutation of the bytearray (by leaving that to the Python interpreter). |
| 653 | +fn bytearray_to_str<'py>(bytearray: &Bound<'py, PyByteArray>) -> PyResult<Bound<'py, PyString>> { |
| 654 | + let py = bytearray.py(); |
| 655 | + let py_string = bytearray |
| 656 | + .call_method1(intern!(py, "decode"), (intern!(py, "utf-8"),))? |
| 657 | + .downcast_into()?; |
| 658 | + Ok(py_string) |
| 659 | +} |
| 660 | + |
655 | 661 | /// Utility for extracting an enum value, if possible.
|
656 | 662 | fn maybe_as_enum<'py>(v: &Bound<'py, PyAny>) -> Option<Bound<'py, PyAny>> {
|
657 | 663 | let py = v.py();
|
|
0 commit comments