|
1 |
| -use std::borrow::Cow; |
2 | 1 | use std::str::from_utf8;
|
3 | 2 |
|
4 | 3 | use pyo3::intern;
|
@@ -144,12 +143,8 @@ impl<'py> Input<'py> for Bound<'py, PyAny> {
|
144 | 143 | Err(_) => Err(ValError::new(ErrorTypeDefaults::StringUnicode, self)),
|
145 | 144 | }
|
146 | 145 | } else if let Ok(py_byte_array) = self.downcast::<PyByteArray>() {
|
147 |
| - // Safety: the gil is held while from_utf8 is running so py_byte_array is not mutated, |
148 |
| - // and we immediately copy the bytes into a new Python string |
149 |
| - match from_utf8(unsafe { py_byte_array.as_bytes() }) { |
150 |
| - // Why Python not Rust? to avoid an unnecessary allocation on the Rust side, the |
151 |
| - // final output needs to be Python anyway. |
152 |
| - Ok(s) => Ok(PyString::new_bound(self.py(), s).into()), |
| 146 | + match bytearray_to_str(py_byte_array) { |
| 147 | + Ok(py_str) => Ok(py_str.into()), |
153 | 148 | Err(_) => Err(ValError::new(ErrorTypeDefaults::StringUnicode, self)),
|
154 | 149 | }
|
155 | 150 | } else if coerce_numbers_to_str && !self.is_exact_instance_of::<PyBool>() && {
|
@@ -204,8 +199,8 @@ impl<'py> Input<'py> for Bound<'py, PyAny> {
|
204 | 199 | }
|
205 | 200 |
|
206 | 201 | if !strict {
|
207 |
| - if let Some(cow_str) = maybe_as_string(self, ErrorTypeDefaults::BoolParsing)? { |
208 |
| - return str_as_bool(self, &cow_str).map(ValidationMatch::lax); |
| 202 | + if let Some(s) = maybe_as_string(self, ErrorTypeDefaults::BoolParsing)? { |
| 203 | + return str_as_bool(self, s).map(ValidationMatch::lax); |
209 | 204 | } else if let Some(int) = extract_i64(self) {
|
210 | 205 | return int_as_bool(self, int).map(ValidationMatch::lax);
|
211 | 206 | } else if let Ok(float) = self.extract::<f64>() {
|
@@ -241,8 +236,8 @@ impl<'py> Input<'py> for Bound<'py, PyAny> {
|
241 | 236 |
|
242 | 237 | 'lax: {
|
243 | 238 | if !strict {
|
244 |
| - return if let Some(cow_str) = maybe_as_string(self, ErrorTypeDefaults::IntParsing)? { |
245 |
| - str_as_int(self, &cow_str) |
| 239 | + return if let Some(s) = maybe_as_string(self, ErrorTypeDefaults::IntParsing)? { |
| 240 | + str_as_int(self, s) |
246 | 241 | } else if self.is_exact_instance_of::<PyFloat>() {
|
247 | 242 | float_as_int(self, self.extract::<f64>()?)
|
248 | 243 | } else if let Ok(decimal) = self.strict_decimal(self.py()) {
|
@@ -283,9 +278,9 @@ impl<'py> Input<'py> for Bound<'py, PyAny> {
|
283 | 278 | }
|
284 | 279 |
|
285 | 280 | if !strict {
|
286 |
| - if let Some(cow_str) = maybe_as_string(self, ErrorTypeDefaults::FloatParsing)? { |
| 281 | + if let Some(s) = maybe_as_string(self, ErrorTypeDefaults::FloatParsing)? { |
287 | 282 | // checking for bytes and string is fast, so do this before isinstance(float)
|
288 |
| - return str_as_float(self, &cow_str).map(ValidationMatch::lax); |
| 283 | + return str_as_float(self, s).map(ValidationMatch::lax); |
289 | 284 | }
|
290 | 285 | }
|
291 | 286 |
|
@@ -630,20 +625,31 @@ fn from_attributes_applicable(obj: &Bound<'_, PyAny>) -> bool {
|
630 | 625 | }
|
631 | 626 |
|
632 | 627 | /// Utility for extracting a string from a PyAny, if possible.
|
633 |
| -fn maybe_as_string<'a>(v: &'a Bound<'_, PyAny>, unicode_error: ErrorType) -> ValResult<Option<Cow<'a, str>>> { |
| 628 | +fn maybe_as_string<'a>(v: &'a Bound<'_, PyAny>, unicode_error: ErrorType) -> ValResult<Option<&'a str>> { |
634 | 629 | if let Ok(py_string) = v.downcast::<PyString>() {
|
635 |
| - let str = py_string_str(py_string)?; |
636 |
| - Ok(Some(Cow::Borrowed(str))) |
| 630 | + py_string_str(py_string).map(Some) |
637 | 631 | } else if let Ok(bytes) = v.downcast::<PyBytes>() {
|
638 | 632 | match from_utf8(bytes.as_bytes()) {
|
639 |
| - Ok(s) => Ok(Some(Cow::Owned(s.to_string()))), |
| 633 | + Ok(s) => Ok(Some(s)), |
640 | 634 | Err(_) => Err(ValError::new(unicode_error, v)),
|
641 | 635 | }
|
642 | 636 | } else {
|
643 | 637 | Ok(None)
|
644 | 638 | }
|
645 | 639 | }
|
646 | 640 |
|
| 641 | +/// Decode a Python bytearray to a Python string. |
| 642 | +/// |
| 643 | +/// Using Python's built-in machinery for this should be efficient and avoids questions around |
| 644 | +/// safety of concurrent mutation of the bytearray (by leaving that to the Python interpreter). |
| 645 | +fn bytearray_to_str<'py>(bytearray: &Bound<'py, PyByteArray>) -> PyResult<Bound<'py, PyString>> { |
| 646 | + let py = bytearray.py(); |
| 647 | + let py_string = bytearray |
| 648 | + .call_method1(intern!(py, "decode"), (intern!(py, "utf-8"),))? |
| 649 | + .downcast_into()?; |
| 650 | + Ok(py_string) |
| 651 | +} |
| 652 | + |
647 | 653 | /// Utility for extracting an enum value, if possible.
|
648 | 654 | fn maybe_as_enum<'py>(v: &Bound<'py, PyAny>) -> Option<Bound<'py, PyAny>> {
|
649 | 655 | let py = v.py();
|
|
0 commit comments