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
27pub 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 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")]
98pub(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")]
217pub(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 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 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 fn get_config<'py>(&self, py: Python<'py>) -> Result<Bound<'py, PyDict>, PyErr> {
354 self.codec.get_config(py)
355 }
356
357 #[classmethod]
358 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 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 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}