1use std::fmt::Display;
2use std::hash::{DefaultHasher, Hash, Hasher};
3use std::sync::Arc;
4
5use arrow_schema::{DataType, Field, IntervalUnit, TimeUnit};
6use pyo3::exceptions::{PyTypeError, PyValueError};
7use pyo3::intern;
8use pyo3::prelude::*;
9use pyo3::types::{PyCapsule, PyTuple, PyType};
10
11use crate::error::PyArrowResult;
12use crate::export::{Arro3DataType, Arro3Field};
13use crate::ffi::from_python::utils::import_schema_pycapsule;
14use crate::ffi::to_python::nanoarrow::to_nanoarrow_schema;
15use crate::ffi::to_schema_pycapsule;
16use crate::PyField;
17
18struct PyTimeUnit(arrow_schema::TimeUnit);
19
20impl<'a> FromPyObject<'_, 'a> for PyTimeUnit {
21 type Error = PyErr;
22
23 fn extract(obj: Borrowed<'_, 'a, PyAny>) -> Result<Self, Self::Error> {
24 let s: String = obj.extract()?;
25 match s.to_lowercase().as_str() {
26 "s" => Ok(Self(TimeUnit::Second)),
27 "ms" => Ok(Self(TimeUnit::Millisecond)),
28 "us" => Ok(Self(TimeUnit::Microsecond)),
29 "ns" => Ok(Self(TimeUnit::Nanosecond)),
30 _ => Err(PyValueError::new_err("Unexpected time unit")),
31 }
32 }
33}
34
35#[derive(PartialEq, Eq, Debug)]
37#[pyclass(module = "arro3.core._core", name = "DataType", subclass, frozen)]
38pub struct PyDataType(DataType);
39
40impl PyDataType {
41 pub fn new(data_type: DataType) -> Self {
43 Self(data_type)
44 }
45
46 pub fn from_arrow_pycapsule(capsule: &Bound<PyCapsule>) -> PyResult<Self> {
48 let schema_ptr = import_schema_pycapsule(capsule)?;
49 let data_type =
50 DataType::try_from(schema_ptr).map_err(|err| PyTypeError::new_err(err.to_string()))?;
51 Ok(Self::new(data_type))
52 }
53
54 pub fn into_inner(self) -> DataType {
56 self.0
57 }
58
59 pub fn to_arro3<'py>(&'py self, py: Python<'py>) -> PyResult<Bound<'py, PyAny>> {
61 let arro3_mod = py.import(intern!(py, "arro3.core"))?;
62 arro3_mod.getattr(intern!(py, "DataType"))?.call_method1(
63 intern!(py, "from_arrow_pycapsule"),
64 PyTuple::new(py, vec![self.__arrow_c_schema__(py)?])?,
65 )
66 }
67
68 pub fn into_arro3(self, py: Python) -> PyResult<Bound<PyAny>> {
70 let arro3_mod = py.import(intern!(py, "arro3.core"))?;
71 let capsule = to_schema_pycapsule(py, &self.0)?;
72 arro3_mod.getattr(intern!(py, "DataType"))?.call_method1(
73 intern!(py, "from_arrow_pycapsule"),
74 PyTuple::new(py, vec![capsule])?,
75 )
76 }
77
78 pub fn to_nanoarrow<'py>(&'py self, py: Python<'py>) -> PyResult<Bound<'py, PyAny>> {
80 to_nanoarrow_schema(py, &self.__arrow_c_schema__(py)?)
81 }
82
83 pub fn into_pyarrow(self, py: Python) -> PyResult<Bound<PyAny>> {
87 let pyarrow_mod = py.import(intern!(py, "pyarrow"))?;
88 let pyarrow_field = pyarrow_mod
89 .getattr(intern!(py, "field"))?
90 .call1(PyTuple::new(py, vec![self.into_pyobject(py)?])?)?;
91 pyarrow_field.getattr(intern!(py, "type"))
92 }
93}
94
95impl From<PyDataType> for DataType {
96 fn from(value: PyDataType) -> Self {
97 value.0
98 }
99}
100
101impl From<DataType> for PyDataType {
102 fn from(value: DataType) -> Self {
103 Self(value)
104 }
105}
106
107impl From<&DataType> for PyDataType {
108 fn from(value: &DataType) -> Self {
109 Self(value.clone())
110 }
111}
112
113impl AsRef<DataType> for PyDataType {
114 fn as_ref(&self) -> &DataType {
115 &self.0
116 }
117}
118
119impl Display for PyDataType {
120 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
121 write!(f, "arro3.core.DataType<")?;
122 self.0.fmt(f)?;
123 writeln!(f, ">")?;
124 Ok(())
125 }
126}
127
128#[allow(non_snake_case)]
129#[pymethods]
130impl PyDataType {
131 pub(crate) fn __arrow_c_schema__<'py>(
132 &'py self,
133 py: Python<'py>,
134 ) -> PyArrowResult<Bound<'py, PyCapsule>> {
135 to_schema_pycapsule(py, &self.0)
136 }
137
138 fn __eq__(&self, other: PyDataType) -> bool {
139 self.equals(other, false)
140 }
141
142 fn __hash__(&self) -> u64 {
143 let mut hasher = DefaultHasher::new();
144 self.0.hash(&mut hasher);
145 hasher.finish()
146 }
147
148 fn __repr__(&self) -> String {
149 self.to_string()
150 }
151
152 #[classmethod]
153 fn from_arrow(_cls: &Bound<PyType>, input: Self) -> Self {
154 input
155 }
156
157 #[classmethod]
158 #[pyo3(name = "from_arrow_pycapsule")]
159 fn from_arrow_pycapsule_py(_cls: &Bound<PyType>, capsule: &Bound<PyCapsule>) -> PyResult<Self> {
160 Self::from_arrow_pycapsule(capsule)
161 }
162
163 #[getter]
164 fn bit_width(&self) -> Option<usize> {
165 self.0.primitive_width().map(|width| width * 8)
166 }
167
168 #[pyo3(signature=(other, *, check_metadata=false))]
169 fn equals(&self, other: PyDataType, check_metadata: bool) -> bool {
170 let other = other.into_inner();
171 if check_metadata {
172 self.0 == other
173 } else {
174 self.0.equals_datatype(&other)
175 }
176 }
177
178 #[getter]
179 fn list_size(&self) -> Option<i32> {
180 match &self.0 {
181 DataType::FixedSizeList(_, list_size) => Some(*list_size),
182 _ => None,
183 }
184 }
185
186 #[getter]
187 fn num_fields(&self) -> usize {
188 match &self.0 {
189 DataType::Null
190 | DataType::Boolean
191 | DataType::Int8
192 | DataType::Int16
193 | DataType::Int32
194 | DataType::Int64
195 | DataType::UInt8
196 | DataType::UInt16
197 | DataType::UInt32
198 | DataType::UInt64
199 | DataType::Float16
200 | DataType::Float32
201 | DataType::Float64
202 | DataType::Timestamp(_, _)
203 | DataType::Date32
204 | DataType::Date64
205 | DataType::Time32(_)
206 | DataType::Time64(_)
207 | DataType::Duration(_)
208 | DataType::Interval(_)
209 | DataType::Binary
210 | DataType::FixedSizeBinary(_)
211 | DataType::LargeBinary
212 | DataType::BinaryView
213 | DataType::Utf8
214 | DataType::LargeUtf8
215 | DataType::Utf8View
216 | DataType::Decimal32(_, _)
217 | DataType::Decimal64(_, _)
218 | DataType::Decimal128(_, _)
219 | DataType::Decimal256(_, _) => 0,
220 DataType::List(_)
221 | DataType::ListView(_)
222 | DataType::FixedSizeList(_, _)
223 | DataType::LargeList(_)
224 | DataType::LargeListView(_) => 1,
225 DataType::Struct(fields) => fields.len(),
226 DataType::Union(fields, _) => fields.len(),
227 DataType::Dictionary(_, _) | DataType::Map(_, _) | DataType::RunEndEncoded(_, _) => 2,
229 }
230 }
231
232 #[getter]
233 fn time_unit(&self) -> Option<&str> {
234 match &self.0 {
235 DataType::Time32(unit)
236 | DataType::Time64(unit)
237 | DataType::Timestamp(unit, _)
238 | DataType::Duration(unit) => match unit {
239 TimeUnit::Second => Some("s"),
240 TimeUnit::Millisecond => Some("ms"),
241 TimeUnit::Microsecond => Some("us"),
242 TimeUnit::Nanosecond => Some("ns"),
243 },
244 _ => None,
245 }
246 }
247
248 #[getter]
249 fn tz(&self) -> Option<&str> {
250 match &self.0 {
251 DataType::Timestamp(_, tz) => tz.as_deref(),
252 _ => None,
253 }
254 }
255
256 #[getter]
257 fn value_type(&self) -> Option<Arro3DataType> {
258 match &self.0 {
259 DataType::FixedSizeList(value_field, _)
260 | DataType::List(value_field)
261 | DataType::LargeList(value_field)
262 | DataType::ListView(value_field)
263 | DataType::LargeListView(value_field)
264 | DataType::RunEndEncoded(_, value_field) => {
265 Some(PyDataType::new(value_field.data_type().clone()).into())
266 }
267 DataType::Dictionary(_key_type, value_type) => {
268 Some(PyDataType::new(*value_type.clone()).into())
269 }
270 _ => None,
271 }
272 }
273
274 #[getter]
275 fn value_field(&self) -> Option<Arro3Field> {
276 match &self.0 {
277 DataType::FixedSizeList(value_field, _)
278 | DataType::List(value_field)
279 | DataType::LargeList(value_field)
280 | DataType::ListView(value_field)
281 | DataType::LargeListView(value_field) => {
282 Some(PyField::new(value_field.clone()).into())
283 }
284 _ => None,
285 }
286 }
287
288 #[getter]
289 fn fields(&self) -> Option<Vec<Arro3Field>> {
290 match &self.0 {
291 DataType::Struct(fields) => Some(
292 fields
293 .into_iter()
294 .map(|f| PyField::new(f.clone()).into())
295 .collect::<Vec<_>>(),
296 ),
297 _ => None,
298 }
299 }
300
301 #[classmethod]
304 fn null(_: &Bound<PyType>) -> Self {
305 Self(DataType::Null)
306 }
307
308 #[classmethod]
309 fn bool(_: &Bound<PyType>) -> Self {
310 Self(DataType::Boolean)
311 }
312
313 #[classmethod]
314 fn int8(_: &Bound<PyType>) -> Self {
315 Self(DataType::Int8)
316 }
317
318 #[classmethod]
319 fn int16(_: &Bound<PyType>) -> Self {
320 Self(DataType::Int16)
321 }
322
323 #[classmethod]
324 fn int32(_: &Bound<PyType>) -> Self {
325 Self(DataType::Int32)
326 }
327
328 #[classmethod]
329 fn int64(_: &Bound<PyType>) -> Self {
330 Self(DataType::Int64)
331 }
332
333 #[classmethod]
334 fn uint8(_: &Bound<PyType>) -> Self {
335 Self(DataType::UInt8)
336 }
337
338 #[classmethod]
339 fn uint16(_: &Bound<PyType>) -> Self {
340 Self(DataType::UInt16)
341 }
342
343 #[classmethod]
344 fn uint32(_: &Bound<PyType>) -> Self {
345 Self(DataType::UInt32)
346 }
347
348 #[classmethod]
349 fn uint64(_: &Bound<PyType>) -> Self {
350 Self(DataType::UInt64)
351 }
352
353 #[classmethod]
354 fn float16(_: &Bound<PyType>) -> Self {
355 Self(DataType::Float16)
356 }
357
358 #[classmethod]
359 fn float32(_: &Bound<PyType>) -> Self {
360 Self(DataType::Float32)
361 }
362
363 #[classmethod]
364 fn float64(_: &Bound<PyType>) -> Self {
365 Self(DataType::Float64)
366 }
367
368 #[classmethod]
369 fn time32(_: &Bound<PyType>, unit: PyTimeUnit) -> PyArrowResult<Self> {
370 if unit.0 == TimeUnit::Microsecond || unit.0 == TimeUnit::Nanosecond {
371 return Err(PyValueError::new_err("Unexpected timeunit for time32").into());
372 }
373
374 Ok(Self(DataType::Time32(unit.0)))
375 }
376
377 #[classmethod]
378 fn time64(_: &Bound<PyType>, unit: PyTimeUnit) -> PyArrowResult<Self> {
379 if unit.0 == TimeUnit::Second || unit.0 == TimeUnit::Millisecond {
380 return Err(PyValueError::new_err("Unexpected timeunit for time64").into());
381 }
382
383 Ok(Self(DataType::Time64(unit.0)))
384 }
385
386 #[classmethod]
387 #[pyo3(signature = (unit, *, tz=None))]
388 fn timestamp(_: &Bound<PyType>, unit: PyTimeUnit, tz: Option<String>) -> Self {
389 Self(DataType::Timestamp(unit.0, tz.map(|s| s.into())))
390 }
391
392 #[classmethod]
393 fn date32(_: &Bound<PyType>) -> Self {
394 Self(DataType::Date32)
395 }
396
397 #[classmethod]
398 fn date64(_: &Bound<PyType>) -> Self {
399 Self(DataType::Date64)
400 }
401
402 #[classmethod]
403 fn duration(_: &Bound<PyType>, unit: PyTimeUnit) -> Self {
404 Self(DataType::Duration(unit.0))
405 }
406
407 #[classmethod]
408 fn month_day_nano_interval(_: &Bound<PyType>) -> Self {
409 Self(DataType::Interval(IntervalUnit::MonthDayNano))
410 }
411
412 #[classmethod]
413 #[pyo3(signature = (length=None))]
414 fn binary(_: &Bound<PyType>, length: Option<i32>) -> Self {
415 if let Some(length) = length {
416 Self(DataType::FixedSizeBinary(length))
417 } else {
418 Self(DataType::Binary)
419 }
420 }
421
422 #[classmethod]
423 fn string(_: &Bound<PyType>) -> Self {
424 Self(DataType::Utf8)
425 }
426
427 #[classmethod]
428 fn utf8(_: &Bound<PyType>) -> Self {
429 Self(DataType::Utf8)
430 }
431
432 #[classmethod]
433 fn large_binary(_: &Bound<PyType>) -> Self {
434 Self(DataType::LargeBinary)
435 }
436
437 #[classmethod]
438 fn large_string(_: &Bound<PyType>) -> Self {
439 Self(DataType::LargeUtf8)
440 }
441
442 #[classmethod]
443 fn large_utf8(_: &Bound<PyType>) -> Self {
444 Self(DataType::LargeUtf8)
445 }
446
447 #[classmethod]
448 fn binary_view(_: &Bound<PyType>) -> Self {
449 Self(DataType::BinaryView)
450 }
451
452 #[classmethod]
453 fn string_view(_: &Bound<PyType>) -> Self {
454 Self(DataType::Utf8View)
455 }
456
457 #[classmethod]
458 fn decimal128(_: &Bound<PyType>, precision: u8, scale: i8) -> Self {
459 Self(DataType::Decimal128(precision, scale))
460 }
461
462 #[classmethod]
463 fn decimal256(_: &Bound<PyType>, precision: u8, scale: i8) -> Self {
464 Self(DataType::Decimal256(precision, scale))
465 }
466
467 #[classmethod]
468 #[pyo3(signature = (value_type, list_size=None))]
469 fn list(_: &Bound<PyType>, value_type: PyField, list_size: Option<i32>) -> Self {
470 if let Some(list_size) = list_size {
471 Self(DataType::FixedSizeList(value_type.into(), list_size))
472 } else {
473 Self(DataType::List(value_type.into()))
474 }
475 }
476
477 #[classmethod]
478 fn large_list(_: &Bound<PyType>, value_type: PyField) -> Self {
479 Self(DataType::LargeList(value_type.into()))
480 }
481
482 #[classmethod]
483 fn list_view(_: &Bound<PyType>, value_type: PyField) -> Self {
484 Self(DataType::ListView(value_type.into()))
485 }
486
487 #[classmethod]
488 fn large_list_view(_: &Bound<PyType>, value_type: PyField) -> Self {
489 Self(DataType::LargeListView(value_type.into()))
490 }
491
492 #[classmethod]
493 fn map(_: &Bound<PyType>, key_type: PyField, item_type: PyField, keys_sorted: bool) -> Self {
494 let data_type = DataType::Map(
497 Arc::new(Field::new(
498 "entries",
499 DataType::Struct(vec![key_type.into_inner(), item_type.into_inner()].into()),
500 false, )),
502 keys_sorted,
503 );
504 Self(data_type)
505 }
506
507 #[classmethod]
508 fn r#struct(_: &Bound<PyType>, fields: Vec<PyField>) -> Self {
509 Self(DataType::Struct(
510 fields.into_iter().map(|field| field.into_inner()).collect(),
511 ))
512 }
513
514 #[classmethod]
515 fn dictionary(_: &Bound<PyType>, index_type: PyDataType, value_type: PyDataType) -> Self {
516 Self(DataType::Dictionary(
517 Box::new(index_type.into_inner()),
518 Box::new(value_type.into_inner()),
519 ))
520 }
521
522 #[classmethod]
523 fn run_end_encoded(_: &Bound<PyType>, run_end_type: PyField, value_type: PyField) -> Self {
524 Self(DataType::RunEndEncoded(
525 run_end_type.into_inner(),
526 value_type.into_inner(),
527 ))
528 }
529
530 #[staticmethod]
533 fn is_boolean(t: PyDataType) -> bool {
534 t.0 == DataType::Boolean
535 }
536
537 #[staticmethod]
538 fn is_integer(t: PyDataType) -> bool {
539 t.0.is_integer()
540 }
541
542 #[staticmethod]
543 fn is_signed_integer(t: PyDataType) -> bool {
544 t.0.is_signed_integer()
545 }
546
547 #[staticmethod]
548 fn is_unsigned_integer(t: PyDataType) -> bool {
549 t.0.is_unsigned_integer()
550 }
551
552 #[staticmethod]
553 fn is_int8(t: PyDataType) -> bool {
554 t.0 == DataType::Int8
555 }
556 #[staticmethod]
557 fn is_int16(t: PyDataType) -> bool {
558 t.0 == DataType::Int16
559 }
560 #[staticmethod]
561 fn is_int32(t: PyDataType) -> bool {
562 t.0 == DataType::Int32
563 }
564 #[staticmethod]
565 fn is_int64(t: PyDataType) -> bool {
566 t.0 == DataType::Int64
567 }
568 #[staticmethod]
569 fn is_uint8(t: PyDataType) -> bool {
570 t.0 == DataType::UInt8
571 }
572 #[staticmethod]
573 fn is_uint16(t: PyDataType) -> bool {
574 t.0 == DataType::UInt16
575 }
576 #[staticmethod]
577 fn is_uint32(t: PyDataType) -> bool {
578 t.0 == DataType::UInt32
579 }
580 #[staticmethod]
581 fn is_uint64(t: PyDataType) -> bool {
582 t.0 == DataType::UInt64
583 }
584 #[staticmethod]
585 fn is_floating(t: PyDataType) -> bool {
586 t.0.is_floating()
587 }
588 #[staticmethod]
589 fn is_float16(t: PyDataType) -> bool {
590 t.0 == DataType::Float16
591 }
592 #[staticmethod]
593 fn is_float32(t: PyDataType) -> bool {
594 t.0 == DataType::Float32
595 }
596 #[staticmethod]
597 fn is_float64(t: PyDataType) -> bool {
598 t.0 == DataType::Float64
599 }
600 #[staticmethod]
601 fn is_decimal(t: PyDataType) -> bool {
602 matches!(t.0, DataType::Decimal128(_, _) | DataType::Decimal256(_, _))
603 }
604 #[staticmethod]
605 fn is_decimal128(t: PyDataType) -> bool {
606 matches!(t.0, DataType::Decimal128(_, _))
607 }
608 #[staticmethod]
609 fn is_decimal256(t: PyDataType) -> bool {
610 matches!(t.0, DataType::Decimal256(_, _))
611 }
612
613 #[staticmethod]
614 fn is_list(t: PyDataType) -> bool {
615 matches!(t.0, DataType::List(_))
616 }
617 #[staticmethod]
618 fn is_large_list(t: PyDataType) -> bool {
619 matches!(t.0, DataType::LargeList(_))
620 }
621 #[staticmethod]
622 fn is_fixed_size_list(t: PyDataType) -> bool {
623 matches!(t.0, DataType::FixedSizeList(_, _))
624 }
625 #[staticmethod]
626 fn is_list_view(t: PyDataType) -> bool {
627 matches!(t.0, DataType::ListView(_))
628 }
629 #[staticmethod]
630 fn is_large_list_view(t: PyDataType) -> bool {
631 matches!(t.0, DataType::LargeListView(_))
632 }
633 #[staticmethod]
634 fn is_struct(t: PyDataType) -> bool {
635 matches!(t.0, DataType::Struct(_))
636 }
637 #[staticmethod]
638 fn is_union(t: PyDataType) -> bool {
639 matches!(t.0, DataType::Union(_, _))
640 }
641 #[staticmethod]
642 fn is_nested(t: PyDataType) -> bool {
643 t.0.is_nested()
644 }
645 #[staticmethod]
646 fn is_run_end_encoded(t: PyDataType) -> bool {
647 t.0.is_run_ends_type()
648 }
649 #[staticmethod]
650 fn is_temporal(t: PyDataType) -> bool {
651 t.0.is_temporal()
652 }
653 #[staticmethod]
654 fn is_timestamp(t: PyDataType) -> bool {
655 matches!(t.0, DataType::Timestamp(_, _))
656 }
657 #[staticmethod]
658 fn is_date(t: PyDataType) -> bool {
659 matches!(t.0, DataType::Date32 | DataType::Date64)
660 }
661 #[staticmethod]
662 fn is_date32(t: PyDataType) -> bool {
663 t.0 == DataType::Date32
664 }
665 #[staticmethod]
666 fn is_date64(t: PyDataType) -> bool {
667 t.0 == DataType::Date64
668 }
669 #[staticmethod]
670 fn is_time(t: PyDataType) -> bool {
671 matches!(t.0, DataType::Time32(_) | DataType::Time64(_))
672 }
673 #[staticmethod]
674 fn is_time32(t: PyDataType) -> bool {
675 matches!(t.0, DataType::Time32(_))
676 }
677 #[staticmethod]
678 fn is_time64(t: PyDataType) -> bool {
679 matches!(t.0, DataType::Time64(_))
680 }
681 #[staticmethod]
682 fn is_duration(t: PyDataType) -> bool {
683 matches!(t.0, DataType::Duration(_))
684 }
685 #[staticmethod]
686 fn is_interval(t: PyDataType) -> bool {
687 matches!(t.0, DataType::Interval(_))
688 }
689 #[staticmethod]
690 fn is_null(t: PyDataType) -> bool {
691 t.0 == DataType::Null
692 }
693 #[staticmethod]
694 fn is_binary(t: PyDataType) -> bool {
695 t.0 == DataType::Binary
696 }
697 #[staticmethod]
698 fn is_unicode(t: PyDataType) -> bool {
699 t.0 == DataType::Utf8
700 }
701 #[staticmethod]
702 fn is_string(t: PyDataType) -> bool {
703 t.0 == DataType::Utf8
704 }
705 #[staticmethod]
706 fn is_large_binary(t: PyDataType) -> bool {
707 t.0 == DataType::LargeBinary
708 }
709 #[staticmethod]
710 fn is_large_unicode(t: PyDataType) -> bool {
711 t.0 == DataType::LargeUtf8
712 }
713 #[staticmethod]
714 fn is_large_string(t: PyDataType) -> bool {
715 t.0 == DataType::LargeUtf8
716 }
717 #[staticmethod]
718 fn is_binary_view(t: PyDataType) -> bool {
719 t.0 == DataType::BinaryView
720 }
721 #[staticmethod]
722 fn is_string_view(t: PyDataType) -> bool {
723 t.0 == DataType::Utf8View
724 }
725 #[staticmethod]
726 fn is_fixed_size_binary(t: PyDataType) -> bool {
727 matches!(t.0, DataType::FixedSizeBinary(_))
728 }
729 #[staticmethod]
730 fn is_map(t: PyDataType) -> bool {
731 matches!(t.0, DataType::Map(_, _))
732 }
733 #[staticmethod]
734 fn is_dictionary(t: PyDataType) -> bool {
735 matches!(t.0, DataType::Dictionary(_, _))
736 }
737 #[staticmethod]
738 fn is_primitive(t: PyDataType) -> bool {
739 t.0.is_primitive()
740 }
741 #[staticmethod]
742 fn is_numeric(t: PyDataType) -> bool {
743 t.0.is_numeric()
744 }
745 #[staticmethod]
746 fn is_dictionary_key_type(t: PyDataType) -> bool {
747 t.0.is_dictionary_key_type()
748 }
749}