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
41 let codec_id_no_rs = codec_id.strip_suffix(".rs").unwrap_or(&codec_id);
43 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 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")]
106pub(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")]
225pub(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 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 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 fn get_config<'py>(&self, py: Python<'py>) -> Result<Bound<'py, PyDict>, PyErr> {
362 self.codec.get_config(py)
363 }
364
365 #[classmethod]
366 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 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 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}