numcodecs_python/
export.rs

1use std::{any::Any, ffi::CString, mem::ManuallyDrop};
2
3use ndarray::{ArrayViewD, ArrayViewMutD, CowArray};
4use numcodecs::{
5    AnyArray, AnyArrayView, AnyArrayViewMut, AnyCowArray, Codec, DynCodec, DynCodecType,
6};
7use numpy::{
8    IxDyn, PyArray, PyArrayDescrMethods, PyArrayDyn, PyArrayMethods, PyUntypedArrayMethods,
9};
10use pyo3::{
11    exceptions::PyTypeError,
12    intern,
13    marker::Ungil,
14    prelude::*,
15    types::{IntoPyDict, PyDict, PyString, PyType},
16    PyTypeInfo,
17};
18use pyo3_error::PyErrChain;
19use pythonize::{pythonize, Depythonizer};
20
21use crate::{
22    schema::{docs_from_schema, signature_from_schema},
23    utils::numpy_asarray,
24    PyCodec, PyCodecClass, PyCodecClassAdapter, PyCodecRegistry,
25};
26
27/// Export the [`DynCodecType`] `ty` to Python by generating a fresh
28/// [`PyCodecClass`] inside `module` and registering it with the
29/// [`PyCodecRegistry`].
30///
31/// # Errors
32///
33/// Errors if generating or exporting the fresh [`PyCodecClass`] fails.
34pub fn export_codec_class<'py, T: DynCodecType<Codec: Ungil> + Ungil>(
35    py: Python<'py>,
36    ty: T,
37    module: Borrowed<'_, 'py, PyModule>,
38) -> Result<Bound<'py, PyCodecClass>, PyErr> {
39    let codec_id = String::from(ty.codec_id());
40
41    // special case for codec ids ending in .rs (we're exporting Rust codecs after all)
42    let codec_id_no_rs = codec_id.strip_suffix(".rs").unwrap_or(&codec_id);
43    // derive the codec name, without any prefix
44    let codec_name = match codec_id_no_rs.rsplit_once('.') {
45        Some((_prefix, name)) => name,
46        None => codec_id_no_rs,
47    };
48    let codec_class_name = convert_case::Casing::to_case(&codec_name, convert_case::Case::Pascal);
49
50    let codec_class: Bound<PyCodecClass> =
51        // re-exporting a Python codec class should roundtrip
52        if let Some(adapter) = (&ty as &dyn Any).downcast_ref::<PyCodecClassAdapter>() {
53            adapter.as_codec_class(py).clone()
54        } else {
55            let codec_config_schema = ty.codec_config_schema();
56
57            let codec_class_bases = (
58                RustCodec::type_object(py),
59                PyCodec::type_object(py),
60            );
61
62            let codec_ty = RustCodecType { ty: ManuallyDrop::new(Box::new(ty)) };
63
64            let codec_class_namespace = [
65                (intern!(py, "__module__"), module.name()?.into_any()),
66                (
67                    intern!(py, "__doc__"),
68                    docs_from_schema(&codec_config_schema).into_pyobject(py)?,
69                ),
70                (
71                    intern!(py, RustCodec::TYPE_ATTRIBUTE),
72                    Bound::new(py, codec_ty)?.into_any(),
73                ),
74                (
75                    intern!(py, "codec_id"),
76                    PyString::new(py, &codec_id).into_any(),
77                ),
78                (
79                    intern!(py, RustCodec::SCHEMA_ATTRIBUTE),
80                    pythonize(py, &codec_config_schema)?,
81                ),
82                (
83                    intern!(py, "__init__"),
84                    py.eval(&CString::new(format!(
85                        "lambda {}: None",
86                        signature_from_schema(&codec_config_schema),
87                    ))?, None, None)?,
88                ),
89            ]
90            .into_py_dict(py)?;
91
92            PyType::type_object(py)
93                .call1((&codec_class_name, codec_class_bases, codec_class_namespace))?
94                .extract()?
95        };
96
97    module.add(codec_class_name.as_str(), &codec_class)?;
98
99    PyCodecRegistry::register_codec(codec_class.as_borrowed(), None)?;
100
101    Ok(codec_class)
102}
103
104#[expect(clippy::redundant_pub_crate)]
105#[pyclass(frozen, module = "numcodecs._rust", name = "_RustCodecType")]
106/// Rust-implemented codec type container.
107pub(crate) struct RustCodecType {
108    ty: ManuallyDrop<Box<dyn AnyCodecType>>,
109}
110
111impl Drop for RustCodecType {
112    fn drop(&mut self) {
113        Python::with_gil(|py| {
114            py.allow_threads(|| {
115                #[allow(unsafe_code)]
116                unsafe {
117                    ManuallyDrop::drop(&mut self.ty);
118                }
119            });
120        });
121    }
122}
123
124impl RustCodecType {
125    pub fn downcast<T: DynCodecType>(&self) -> Option<&T> {
126        self.ty.as_any().downcast_ref()
127    }
128}
129
130trait AnyCodec: 'static + Send + Sync + Ungil {
131    fn encode(&self, py: Python, data: AnyCowArray) -> Result<AnyArray, PyErr>;
132
133    fn decode(&self, py: Python, encoded: AnyCowArray) -> Result<AnyArray, PyErr>;
134
135    fn decode_into(
136        &self,
137        py: Python,
138        encoded: AnyArrayView,
139        decoded: AnyArrayViewMut,
140    ) -> Result<(), PyErr>;
141
142    fn get_config<'py>(&self, py: Python<'py>) -> Result<Bound<'py, PyDict>, PyErr>;
143
144    fn as_any(&self) -> &dyn Any;
145}
146
147impl<T: DynCodec + Ungil> AnyCodec for T {
148    fn encode(&self, py: Python, data: AnyCowArray) -> Result<AnyArray, PyErr> {
149        py.allow_threads(|| <T as Codec>::encode(self, data))
150            .map_err(|err| PyErrChain::pyerr_from_err(py, err))
151    }
152
153    fn decode(&self, py: Python, encoded: AnyCowArray) -> Result<AnyArray, PyErr> {
154        py.allow_threads(|| <T as Codec>::decode(self, encoded))
155            .map_err(|err| PyErrChain::pyerr_from_err(py, err))
156    }
157
158    fn decode_into(
159        &self,
160        py: Python,
161        encoded: AnyArrayView,
162        decoded: AnyArrayViewMut,
163    ) -> Result<(), PyErr> {
164        py.allow_threads(|| <T as Codec>::decode_into(self, encoded, decoded))
165            .map_err(|err| PyErrChain::pyerr_from_err(py, err))
166    }
167
168    fn get_config<'py>(&self, py: Python<'py>) -> Result<Bound<'py, PyDict>, PyErr> {
169        let config: serde_json::Value = py
170            .allow_threads(|| <T as DynCodec>::get_config(self, serde_json::value::Serializer))
171            .map_err(|err| PyErrChain::pyerr_from_err(py, err))?;
172        pythonize::pythonize(py, &config)?.extract()
173    }
174
175    fn as_any(&self) -> &dyn Any {
176        self
177    }
178}
179
180trait AnyCodecType: 'static + Send + Sync + Ungil {
181    fn codec_from_config<'py>(
182        &self,
183        py: Python<'py>,
184        cls_module: String,
185        cls_name: String,
186        config: Bound<'py, PyDict>,
187    ) -> Result<RustCodec, PyErr>;
188
189    fn as_any(&self) -> &dyn Any;
190}
191
192impl<T: DynCodecType + Ungil> AnyCodecType for T {
193    fn codec_from_config<'py>(
194        &self,
195        py: Python<'py>,
196        cls_module: String,
197        cls_name: String,
198        config: Bound<'py, PyDict>,
199    ) -> Result<RustCodec, PyErr> {
200        let config = serde_transcode::transcode(
201            &mut Depythonizer::from_object(config.as_any()),
202            serde_json::value::Serializer,
203        )
204        .map_err(|err| PyErrChain::pyerr_from_err(py, err))?;
205
206        py.allow_threads(|| -> Result<RustCodec, serde_json::Error> {
207            let codec = <T as DynCodecType>::codec_from_config(self, config)?;
208
209            Ok(RustCodec {
210                cls_module,
211                cls_name,
212                codec: ManuallyDrop::new(Box::new(codec)),
213            })
214        })
215        .map_err(|err| PyErrChain::pyerr_from_err(py, err))
216    }
217
218    fn as_any(&self) -> &dyn Any {
219        self
220    }
221}
222
223#[expect(clippy::redundant_pub_crate)]
224#[pyclass(subclass, frozen, module = "numcodecs._rust")]
225/// Rust-implemented codec abstract base class.
226///
227/// This class implements the [`numcodecs.abc.Codec`][numcodecs.abc.Codec] API.
228pub(crate) struct RustCodec {
229    cls_module: String,
230    cls_name: String,
231    codec: ManuallyDrop<Box<dyn AnyCodec>>,
232}
233
234impl Drop for RustCodec {
235    fn drop(&mut self) {
236        Python::with_gil(|py| {
237            py.allow_threads(|| {
238                #[allow(unsafe_code)]
239                unsafe {
240                    ManuallyDrop::drop(&mut self.codec);
241                }
242            });
243        });
244    }
245}
246
247impl RustCodec {
248    pub const SCHEMA_ATTRIBUTE: &'static str = "__schema__";
249    pub const TYPE_ATTRIBUTE: &'static str = "_ty";
250
251    pub fn downcast<T: DynCodec>(&self) -> Option<&T> {
252        self.codec.as_any().downcast_ref()
253    }
254}
255
256#[pymethods]
257impl RustCodec {
258    #[new]
259    #[classmethod]
260    #[pyo3(signature = (**kwargs))]
261    fn new<'py>(
262        cls: &Bound<'py, PyType>,
263        py: Python<'py>,
264        kwargs: Option<Bound<'py, PyDict>>,
265    ) -> Result<Self, PyErr> {
266        let cls: &Bound<PyCodecClass> = cls.downcast()?;
267        let cls_module: String = cls.getattr(intern!(py, "__module__"))?.extract()?;
268        let cls_name: String = cls.getattr(intern!(py, "__name__"))?.extract()?;
269
270        let ty: Bound<RustCodecType> = cls
271            .getattr(intern!(py, RustCodec::TYPE_ATTRIBUTE))
272            .map_err(|_| {
273                PyTypeError::new_err(format!(
274                    "{cls_module}.{cls_name} is not linked to a Rust codec type"
275                ))
276            })?
277            .extract()?;
278        let ty: PyRef<RustCodecType> = ty.try_borrow()?;
279
280        ty.ty.codec_from_config(
281            py,
282            cls_module,
283            cls_name,
284            kwargs.unwrap_or_else(|| PyDict::new(py)),
285        )
286    }
287
288    /// Encode the data in `buf`.
289    ///
290    /// Parameters
291    /// ----------
292    /// buf : Buffer
293    ///     Data to be encoded. May be any object supporting the new-style
294    ///     buffer protocol.
295    ///
296    /// Returns
297    /// -------
298    /// enc : Buffer
299    ///     Encoded data. May be any object supporting the new-style buffer
300    ///     protocol.
301    fn encode<'py>(
302        &self,
303        py: Python<'py>,
304        buf: &Bound<'py, PyAny>,
305    ) -> Result<Bound<'py, PyAny>, PyErr> {
306        self.process(
307            py,
308            buf.as_borrowed(),
309            AnyCodec::encode,
310            &format!("{}.{}::encode", self.cls_module, self.cls_name),
311        )
312    }
313
314    #[pyo3(signature = (buf, out=None))]
315    /// Decode the data in `buf`.
316    ///
317    /// Parameters
318    /// ----------
319    /// buf : Buffer
320    ///     Encoded data. May be any object supporting the new-style buffer
321    ///     protocol.
322    /// out : Buffer, optional
323    ///     Writeable buffer to store decoded data. N.B. if provided, this buffer must
324    ///     be exactly the right size to store the decoded data.
325    ///
326    /// Returns
327    /// -------
328    /// dec : Buffer
329    ///     Decoded data. May be any object supporting the new-style
330    ///     buffer protocol.
331    fn decode<'py>(
332        &self,
333        py: Python<'py>,
334        buf: &Bound<'py, PyAny>,
335        out: Option<Bound<'py, PyAny>>,
336    ) -> Result<Bound<'py, PyAny>, PyErr> {
337        let class_method = &format!("{}.{}::decode", self.cls_module, self.cls_name);
338        if let Some(out) = out {
339            self.process_into(
340                py,
341                buf.as_borrowed(),
342                out.as_borrowed(),
343                AnyCodec::decode_into,
344                class_method,
345            )?;
346            Ok(out)
347        } else {
348            self.process(py, buf.as_borrowed(), AnyCodec::decode, class_method)
349        }
350    }
351
352    /// Returns the configuration of the codec.
353    ///
354    /// [`numcodecs.registry.get_codec(config)`][numcodecs.registry.get_codec]
355    /// can be used to reconstruct this codec from the returned config.
356    ///
357    /// Returns
358    /// -------
359    /// config : dict
360    ///     Configuration of the codec.
361    fn get_config<'py>(&self, py: Python<'py>) -> Result<Bound<'py, PyDict>, PyErr> {
362        self.codec.get_config(py)
363    }
364
365    #[classmethod]
366    /// Instantiate the codec from a configuration [`dict`][dict].
367    ///
368    /// Parameters
369    /// ----------
370    /// config : dict
371    ///     Configuration of the codec.
372    ///
373    /// Returns
374    /// -------
375    /// codec : Self
376    ///     Instantiated codec.
377    fn from_config<'py>(
378        cls: &Bound<'py, PyType>,
379        config: &Bound<'py, PyDict>,
380    ) -> Result<Bound<'py, PyCodec>, PyErr> {
381        let cls: Bound<PyCodecClass> = cls.extract()?;
382
383        // Ensures that cls(**config) is called and an instance of cls is returned
384        cls.call((), Some(config))?.extract()
385    }
386
387    fn __repr__(this: PyRef<Self>, py: Python) -> Result<String, PyErr> {
388        let config = this.get_config(py)?;
389        let Ok(py_this) = this.into_pyobject(py);
390
391        let mut repr = py_this.get_type().name()?.to_cow()?.into_owned();
392        repr.push('(');
393
394        let mut first = true;
395
396        for (name, value) in config.iter() {
397            let name: String = name.extract()?;
398
399            if name == "id" {
400                // Exclude the id config parameter from the repr
401                continue;
402            }
403
404            let value_repr: String = value.repr()?.extract()?;
405
406            if !first {
407                repr.push_str(", ");
408            }
409            first = false;
410
411            repr.push_str(&name);
412            repr.push('=');
413            repr.push_str(&value_repr);
414        }
415
416        repr.push(')');
417
418        Ok(repr)
419    }
420}
421
422impl RustCodec {
423    fn process<'py>(
424        &self,
425        py: Python<'py>,
426        buf: Borrowed<'_, 'py, PyAny>,
427        process: impl FnOnce(&dyn AnyCodec, Python, AnyCowArray) -> Result<AnyArray, PyErr>,
428        class_method: &str,
429    ) -> Result<Bound<'py, PyAny>, PyErr> {
430        Self::with_pyarraylike_as_cow(py, buf, class_method, |data| {
431            let processed = process(&**self.codec, py, data)?;
432            Self::any_array_into_pyarray(py, processed, class_method)
433        })
434    }
435
436    fn process_into<'py>(
437        &self,
438        py: Python<'py>,
439        buf: Borrowed<'_, 'py, PyAny>,
440        out: Borrowed<'_, 'py, PyAny>,
441        process: impl FnOnce(&dyn AnyCodec, Python, AnyArrayView, AnyArrayViewMut) -> Result<(), PyErr>,
442        class_method: &str,
443    ) -> Result<(), PyErr> {
444        Self::with_pyarraylike_as_view(py, buf, class_method, |data| {
445            Self::with_pyarraylike_as_view_mut(py, out, class_method, |data_out| {
446                process(&**self.codec, py, data, data_out)
447            })
448        })
449    }
450
451    fn with_pyarraylike_as_cow<'py, O>(
452        py: Python<'py>,
453        buf: Borrowed<'_, 'py, PyAny>,
454        class_method: &str,
455        with: impl for<'a> FnOnce(AnyCowArray<'a>) -> Result<O, PyErr>,
456    ) -> Result<O, PyErr> {
457        fn with_pyarraylike_as_cow_inner<T: numpy::Element, O>(
458            data: Borrowed<PyArrayDyn<T>>,
459            with: impl for<'a> FnOnce(CowArray<'a, T, IxDyn>) -> Result<O, PyErr>,
460        ) -> Result<O, PyErr> {
461            let readonly_data = data.try_readonly()?;
462            with(readonly_data.as_array().into())
463        }
464
465        let data = numpy_asarray(py, buf)?;
466        let dtype = data.dtype();
467
468        if dtype.is_equiv_to(&numpy::dtype::<u8>(py)) {
469            with_pyarraylike_as_cow_inner(data.downcast::<PyArrayDyn<u8>>()?.into(), |a| {
470                with(AnyCowArray::U8(a))
471            })
472        } else if dtype.is_equiv_to(&numpy::dtype::<u16>(py)) {
473            with_pyarraylike_as_cow_inner(data.downcast::<PyArrayDyn<u16>>()?.into(), |a| {
474                with(AnyCowArray::U16(a))
475            })
476        } else if dtype.is_equiv_to(&numpy::dtype::<u32>(py)) {
477            with_pyarraylike_as_cow_inner(data.downcast::<PyArrayDyn<u32>>()?.into(), |a| {
478                with(AnyCowArray::U32(a))
479            })
480        } else if dtype.is_equiv_to(&numpy::dtype::<u64>(py)) {
481            with_pyarraylike_as_cow_inner(data.downcast::<PyArrayDyn<u64>>()?.into(), |a| {
482                with(AnyCowArray::U64(a))
483            })
484        } else if dtype.is_equiv_to(&numpy::dtype::<i8>(py)) {
485            with_pyarraylike_as_cow_inner(data.downcast::<PyArrayDyn<i8>>()?.into(), |a| {
486                with(AnyCowArray::I8(a))
487            })
488        } else if dtype.is_equiv_to(&numpy::dtype::<i16>(py)) {
489            with_pyarraylike_as_cow_inner(data.downcast::<PyArrayDyn<i16>>()?.into(), |a| {
490                with(AnyCowArray::I16(a))
491            })
492        } else if dtype.is_equiv_to(&numpy::dtype::<i32>(py)) {
493            with_pyarraylike_as_cow_inner(data.downcast::<PyArrayDyn<i32>>()?.into(), |a| {
494                with(AnyCowArray::I32(a))
495            })
496        } else if dtype.is_equiv_to(&numpy::dtype::<i64>(py)) {
497            with_pyarraylike_as_cow_inner(data.downcast::<PyArrayDyn<i64>>()?.into(), |a| {
498                with(AnyCowArray::I64(a))
499            })
500        } else if dtype.is_equiv_to(&numpy::dtype::<f32>(py)) {
501            with_pyarraylike_as_cow_inner(data.downcast::<PyArrayDyn<f32>>()?.into(), |a| {
502                with(AnyCowArray::F32(a))
503            })
504        } else if dtype.is_equiv_to(&numpy::dtype::<f64>(py)) {
505            with_pyarraylike_as_cow_inner(data.downcast::<PyArrayDyn<f64>>()?.into(), |a| {
506                with(AnyCowArray::F64(a))
507            })
508        } else {
509            Err(PyTypeError::new_err(format!(
510                "{class_method} received buffer of unsupported dtype `{dtype}`",
511            )))
512        }
513    }
514
515    fn with_pyarraylike_as_view<'py, O>(
516        py: Python<'py>,
517        buf: Borrowed<'_, 'py, PyAny>,
518        class_method: &str,
519        with: impl for<'a> FnOnce(AnyArrayView<'a>) -> Result<O, PyErr>,
520    ) -> Result<O, PyErr> {
521        fn with_pyarraylike_as_view_inner<T: numpy::Element, O>(
522            data: Borrowed<PyArrayDyn<T>>,
523            with: impl for<'a> FnOnce(ArrayViewD<'a, T>) -> Result<O, PyErr>,
524        ) -> Result<O, PyErr> {
525            let readonly_data = data.try_readonly()?;
526            with(readonly_data.as_array())
527        }
528
529        let data = numpy_asarray(py, buf)?;
530        let dtype = data.dtype();
531
532        if dtype.is_equiv_to(&numpy::dtype::<u8>(py)) {
533            with_pyarraylike_as_view_inner(data.downcast::<PyArrayDyn<u8>>()?.into(), |a| {
534                with(AnyArrayView::U8(a))
535            })
536        } else if dtype.is_equiv_to(&numpy::dtype::<u16>(py)) {
537            with_pyarraylike_as_view_inner(data.downcast::<PyArrayDyn<u16>>()?.into(), |a| {
538                with(AnyArrayView::U16(a))
539            })
540        } else if dtype.is_equiv_to(&numpy::dtype::<u32>(py)) {
541            with_pyarraylike_as_view_inner(data.downcast::<PyArrayDyn<u32>>()?.into(), |a| {
542                with(AnyArrayView::U32(a))
543            })
544        } else if dtype.is_equiv_to(&numpy::dtype::<u64>(py)) {
545            with_pyarraylike_as_view_inner(data.downcast::<PyArrayDyn<u64>>()?.into(), |a| {
546                with(AnyArrayView::U64(a))
547            })
548        } else if dtype.is_equiv_to(&numpy::dtype::<i8>(py)) {
549            with_pyarraylike_as_view_inner(data.downcast::<PyArrayDyn<i8>>()?.into(), |a| {
550                with(AnyArrayView::I8(a))
551            })
552        } else if dtype.is_equiv_to(&numpy::dtype::<i16>(py)) {
553            with_pyarraylike_as_view_inner(data.downcast::<PyArrayDyn<i16>>()?.into(), |a| {
554                with(AnyArrayView::I16(a))
555            })
556        } else if dtype.is_equiv_to(&numpy::dtype::<i32>(py)) {
557            with_pyarraylike_as_view_inner(data.downcast::<PyArrayDyn<i32>>()?.into(), |a| {
558                with(AnyArrayView::I32(a))
559            })
560        } else if dtype.is_equiv_to(&numpy::dtype::<i64>(py)) {
561            with_pyarraylike_as_view_inner(data.downcast::<PyArrayDyn<i64>>()?.into(), |a| {
562                with(AnyArrayView::I64(a))
563            })
564        } else if dtype.is_equiv_to(&numpy::dtype::<f32>(py)) {
565            with_pyarraylike_as_view_inner(data.downcast::<PyArrayDyn<f32>>()?.into(), |a| {
566                with(AnyArrayView::F32(a))
567            })
568        } else if dtype.is_equiv_to(&numpy::dtype::<f64>(py)) {
569            with_pyarraylike_as_view_inner(data.downcast::<PyArrayDyn<f64>>()?.into(), |a| {
570                with(AnyArrayView::F64(a))
571            })
572        } else {
573            Err(PyTypeError::new_err(format!(
574                "{class_method} received buffer of unsupported dtype `{dtype}`",
575            )))
576        }
577    }
578
579    fn with_pyarraylike_as_view_mut<'py, O>(
580        py: Python<'py>,
581        buf: Borrowed<'_, 'py, PyAny>,
582        class_method: &str,
583        with: impl for<'a> FnOnce(AnyArrayViewMut<'a>) -> Result<O, PyErr>,
584    ) -> Result<O, PyErr> {
585        fn with_pyarraylike_as_view_mut_inner<T: numpy::Element, O>(
586            data: Borrowed<PyArrayDyn<T>>,
587            with: impl for<'a> FnOnce(ArrayViewMutD<'a, T>) -> Result<O, PyErr>,
588        ) -> Result<O, PyErr> {
589            let mut readwrite_data = data.try_readwrite()?;
590            with(readwrite_data.as_array_mut())
591        }
592
593        let data = numpy_asarray(py, buf)?;
594        let dtype = data.dtype();
595
596        if dtype.is_equiv_to(&numpy::dtype::<u8>(py)) {
597            with_pyarraylike_as_view_mut_inner(data.downcast::<PyArrayDyn<u8>>()?.into(), |a| {
598                with(AnyArrayViewMut::U8(a))
599            })
600        } else if dtype.is_equiv_to(&numpy::dtype::<u16>(py)) {
601            with_pyarraylike_as_view_mut_inner(data.downcast::<PyArrayDyn<u16>>()?.into(), |a| {
602                with(AnyArrayViewMut::U16(a))
603            })
604        } else if dtype.is_equiv_to(&numpy::dtype::<u32>(py)) {
605            with_pyarraylike_as_view_mut_inner(data.downcast::<PyArrayDyn<u32>>()?.into(), |a| {
606                with(AnyArrayViewMut::U32(a))
607            })
608        } else if dtype.is_equiv_to(&numpy::dtype::<u64>(py)) {
609            with_pyarraylike_as_view_mut_inner(data.downcast::<PyArrayDyn<u64>>()?.into(), |a| {
610                with(AnyArrayViewMut::U64(a))
611            })
612        } else if dtype.is_equiv_to(&numpy::dtype::<i8>(py)) {
613            with_pyarraylike_as_view_mut_inner(data.downcast::<PyArrayDyn<i8>>()?.into(), |a| {
614                with(AnyArrayViewMut::I8(a))
615            })
616        } else if dtype.is_equiv_to(&numpy::dtype::<i16>(py)) {
617            with_pyarraylike_as_view_mut_inner(data.downcast::<PyArrayDyn<i16>>()?.into(), |a| {
618                with(AnyArrayViewMut::I16(a))
619            })
620        } else if dtype.is_equiv_to(&numpy::dtype::<i32>(py)) {
621            with_pyarraylike_as_view_mut_inner(data.downcast::<PyArrayDyn<i32>>()?.into(), |a| {
622                with(AnyArrayViewMut::I32(a))
623            })
624        } else if dtype.is_equiv_to(&numpy::dtype::<i64>(py)) {
625            with_pyarraylike_as_view_mut_inner(data.downcast::<PyArrayDyn<i64>>()?.into(), |a| {
626                with(AnyArrayViewMut::I64(a))
627            })
628        } else if dtype.is_equiv_to(&numpy::dtype::<f32>(py)) {
629            with_pyarraylike_as_view_mut_inner(data.downcast::<PyArrayDyn<f32>>()?.into(), |a| {
630                with(AnyArrayViewMut::F32(a))
631            })
632        } else if dtype.is_equiv_to(&numpy::dtype::<f64>(py)) {
633            with_pyarraylike_as_view_mut_inner(data.downcast::<PyArrayDyn<f64>>()?.into(), |a| {
634                with(AnyArrayViewMut::F64(a))
635            })
636        } else {
637            Err(PyTypeError::new_err(format!(
638                "{class_method} received buffer of unsupported dtype `{dtype}`",
639            )))
640        }
641    }
642
643    fn any_array_into_pyarray<'py>(
644        py: Python<'py>,
645        array: AnyArray,
646        class_method: &str,
647    ) -> Result<Bound<'py, PyAny>, PyErr> {
648        match array {
649            AnyArray::U8(a) => Ok(PyArray::from_owned_array(py, a).into_any()),
650            AnyArray::U16(a) => Ok(PyArray::from_owned_array(py, a).into_any()),
651            AnyArray::U32(a) => Ok(PyArray::from_owned_array(py, a).into_any()),
652            AnyArray::U64(a) => Ok(PyArray::from_owned_array(py, a).into_any()),
653            AnyArray::I8(a) => Ok(PyArray::from_owned_array(py, a).into_any()),
654            AnyArray::I16(a) => Ok(PyArray::from_owned_array(py, a).into_any()),
655            AnyArray::I32(a) => Ok(PyArray::from_owned_array(py, a).into_any()),
656            AnyArray::I64(a) => Ok(PyArray::from_owned_array(py, a).into_any()),
657            AnyArray::F32(a) => Ok(PyArray::from_owned_array(py, a).into_any()),
658            AnyArray::F64(a) => Ok(PyArray::from_owned_array(py, a).into_any()),
659            array => Err(PyTypeError::new_err(format!(
660                "{class_method} returned unsupported dtype `{}`",
661                array.dtype(),
662            ))),
663        }
664    }
665}