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