1pub mod constants;
2mod warning;
3
4use std::borrow::Cow;
5use std::collections::TryReserveError;
6use std::convert::Infallible;
7use std::error::Error;
8use std::fmt::{self, Display, Formatter, Write};
9use std::ops::Deref;
10use std::sync::{Arc, LazyLock};
11use std::{env, io};
12pub mod signals;
13
14pub use warning::*;
15
16#[cfg(feature = "python")]
17mod python;
18
19enum ErrorStrategy {
20 Panic,
21 WithBacktrace,
22 Normal,
23}
24
25static ERROR_STRATEGY: LazyLock<ErrorStrategy> = LazyLock::new(|| {
26 if env::var("POLARS_PANIC_ON_ERR").as_deref() == Ok("1") {
27 ErrorStrategy::Panic
28 } else if env::var("POLARS_BACKTRACE_IN_ERR").as_deref() == Ok("1") {
29 ErrorStrategy::WithBacktrace
30 } else {
31 ErrorStrategy::Normal
32 }
33});
34
35#[derive(Debug, Clone)]
36pub struct ErrString(Cow<'static, str>);
37
38impl ErrString {
39 pub const fn new_static(s: &'static str) -> Self {
40 Self(Cow::Borrowed(s))
41 }
42}
43
44impl<T> From<T> for ErrString
45where
46 T: Into<Cow<'static, str>>,
47{
48 #[track_caller]
49 fn from(msg: T) -> Self {
50 match &*ERROR_STRATEGY {
51 ErrorStrategy::Panic => panic!("{}", msg.into()),
52 ErrorStrategy::WithBacktrace => ErrString(Cow::Owned(format!(
53 "{}\n\nRust backtrace:\n{}",
54 msg.into(),
55 std::backtrace::Backtrace::force_capture()
56 ))),
57 ErrorStrategy::Normal => ErrString(msg.into()),
58 }
59 }
60}
61
62impl AsRef<str> for ErrString {
63 fn as_ref(&self) -> &str {
64 &self.0
65 }
66}
67
68impl Deref for ErrString {
69 type Target = str;
70
71 fn deref(&self) -> &Self::Target {
72 &self.0
73 }
74}
75
76impl Display for ErrString {
77 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
78 write!(f, "{}", self.0)
79 }
80}
81
82#[derive(Debug, Clone)]
83pub enum PolarsError {
84 AssertionError(ErrString),
85 ColumnNotFound(ErrString),
86 ComputeError(ErrString),
87 Duplicate(ErrString),
88 InvalidOperation(ErrString),
89 IO {
90 error: Arc<io::Error>,
91 msg: Option<ErrString>,
92 },
93 NoData(ErrString),
94 OutOfBounds(ErrString),
95 SchemaFieldNotFound(ErrString),
96 SchemaMismatch(ErrString),
97 ShapeMismatch(ErrString),
98 SQLInterface(ErrString),
99 SQLSyntax(ErrString),
100 StringCacheMismatch(ErrString),
101 StructFieldNotFound(ErrString),
102 Context {
103 error: Box<PolarsError>,
104 msg: ErrString,
105 },
106 #[cfg(feature = "python")]
107 Python {
108 error: python::PyErrWrap,
109 },
110}
111
112impl Error for PolarsError {}
113
114impl Display for PolarsError {
115 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
116 use PolarsError::*;
117 match self {
118 ComputeError(msg)
119 | InvalidOperation(msg)
120 | OutOfBounds(msg)
121 | SchemaMismatch(msg)
122 | SQLInterface(msg)
123 | SQLSyntax(msg) => write!(f, "{msg}"),
124
125 AssertionError(msg) => write!(f, "assertion failed: {msg}"),
126 ColumnNotFound(msg) => write!(f, "not found: {msg}"),
127 Duplicate(msg) => write!(f, "duplicate: {msg}"),
128 IO { error, msg } => match msg {
129 Some(m) => write!(f, "{m}"),
130 None => write!(f, "{error}"),
131 },
132 NoData(msg) => write!(f, "no data: {msg}"),
133 SchemaFieldNotFound(msg) => write!(f, "field not found: {msg}"),
134 ShapeMismatch(msg) => write!(f, "lengths don't match: {msg}"),
135 StringCacheMismatch(msg) => write!(f, "string caches don't match: {msg}"),
136 StructFieldNotFound(msg) => write!(f, "field not found: {msg}"),
137 Context { error, msg } => write!(f, "{error}: {msg}"),
138 #[cfg(feature = "python")]
139 Python { error } => write!(f, "python: {error}"),
140 }
141 }
142}
143
144impl From<io::Error> for PolarsError {
145 fn from(value: io::Error) -> Self {
146 PolarsError::IO {
147 error: Arc::new(value),
148 msg: None,
149 }
150 }
151}
152
153#[cfg(feature = "regex")]
154impl From<regex::Error> for PolarsError {
155 fn from(err: regex::Error) -> Self {
156 PolarsError::ComputeError(format!("regex error: {err}").into())
157 }
158}
159
160#[cfg(feature = "object_store")]
161impl From<object_store::Error> for PolarsError {
162 fn from(err: object_store::Error) -> Self {
163 if let object_store::Error::Generic { store, source } = &err {
164 if let Some(polars_err) = source.as_ref().downcast_ref::<PolarsError>() {
165 return polars_err.wrap_msg(|s| format!("{s} (store: {store})"));
166 }
167 }
168
169 std::io::Error::other(format!("object-store error: {err}")).into()
170 }
171}
172
173#[cfg(feature = "avro-schema")]
174impl From<avro_schema::error::Error> for PolarsError {
175 fn from(value: avro_schema::error::Error) -> Self {
176 polars_err!(ComputeError: "avro-error: {}", value)
177 }
178}
179
180impl From<simdutf8::basic::Utf8Error> for PolarsError {
181 fn from(value: simdutf8::basic::Utf8Error) -> Self {
182 polars_err!(ComputeError: "invalid utf8: {}", value)
183 }
184}
185#[cfg(feature = "arrow-format")]
186impl From<arrow_format::ipc::planus::Error> for PolarsError {
187 fn from(err: arrow_format::ipc::planus::Error) -> Self {
188 polars_err!(ComputeError: "parquet error: {err:?}")
189 }
190}
191
192impl From<TryReserveError> for PolarsError {
193 fn from(value: TryReserveError) -> Self {
194 polars_err!(ComputeError: "OOM: {}", value)
195 }
196}
197
198impl From<Infallible> for PolarsError {
199 fn from(_: Infallible) -> Self {
200 unreachable!()
201 }
202}
203
204pub type PolarsResult<T> = Result<T, PolarsError>;
205
206impl PolarsError {
207 pub fn context_trace(self) -> Self {
208 use PolarsError::*;
209 match self {
210 Context { error, msg } => {
211 if !matches!(&*error, PolarsError::Context { .. }) {
213 return *error;
214 }
215 let mut current_error = &*error;
216 let material_error = error.get_err();
217
218 let mut messages = vec![&msg];
219
220 while let PolarsError::Context { msg, error } = current_error {
221 current_error = error;
222 messages.push(msg)
223 }
224
225 let mut bt = String::new();
226
227 let mut count = 0;
228 while let Some(msg) = messages.pop() {
229 count += 1;
230 writeln!(&mut bt, "\t[{count}] {msg}").unwrap();
231 }
232 material_error.wrap_msg(move |msg| {
233 format!("{msg}\n\nThis error occurred with the following context stack:\n{bt}")
234 })
235 },
236 err => err,
237 }
238 }
239
240 pub fn wrap_msg<F: FnOnce(&str) -> String>(&self, func: F) -> Self {
241 use PolarsError::*;
242 match self {
243 AssertionError(msg) => AssertionError(func(msg).into()),
244 ColumnNotFound(msg) => ColumnNotFound(func(msg).into()),
245 ComputeError(msg) => ComputeError(func(msg).into()),
246 Duplicate(msg) => Duplicate(func(msg).into()),
247 InvalidOperation(msg) => InvalidOperation(func(msg).into()),
248 IO { error, msg } => {
249 let msg = match msg {
250 Some(msg) => func(msg),
251 None => func(&format!("{error}")),
252 };
253 IO {
254 error: error.clone(),
255 msg: Some(msg.into()),
256 }
257 },
258 NoData(msg) => NoData(func(msg).into()),
259 OutOfBounds(msg) => OutOfBounds(func(msg).into()),
260 SchemaFieldNotFound(msg) => SchemaFieldNotFound(func(msg).into()),
261 SchemaMismatch(msg) => SchemaMismatch(func(msg).into()),
262 ShapeMismatch(msg) => ShapeMismatch(func(msg).into()),
263 StringCacheMismatch(msg) => StringCacheMismatch(func(msg).into()),
264 StructFieldNotFound(msg) => StructFieldNotFound(func(msg).into()),
265 SQLInterface(msg) => SQLInterface(func(msg).into()),
266 SQLSyntax(msg) => SQLSyntax(func(msg).into()),
267 Context { error, .. } => error.wrap_msg(func),
268 #[cfg(feature = "python")]
269 Python { error } => pyo3::Python::attach(|py| {
270 use pyo3::types::{PyAnyMethods, PyStringMethods};
271 use pyo3::{IntoPyObject, PyErr};
272
273 let value = error.value(py);
274
275 let msg = if let Ok(s) = value.str() {
276 func(&s.to_string_lossy())
277 } else {
278 func("<exception str() failed>")
279 };
280
281 let cls = value.get_type();
282
283 let out = PyErr::from_type(cls, (msg,));
284
285 let out = if let Ok(out_with_traceback) = (|| {
286 out.clone_ref(py)
287 .into_pyobject(py)?
288 .getattr("with_traceback")
289 .unwrap()
290 .call1((value.getattr("__traceback__").unwrap(),))
291 })() {
292 PyErr::from_value(out_with_traceback)
293 } else {
294 out
295 };
296
297 Python {
298 error: python::PyErrWrap(out),
299 }
300 }),
301 }
302 }
303
304 fn get_err(&self) -> &Self {
305 use PolarsError::*;
306 match self {
307 Context { error, .. } => error.get_err(),
308 err => err,
309 }
310 }
311
312 pub fn context(self, msg: ErrString) -> Self {
313 PolarsError::Context {
314 msg,
315 error: Box::new(self),
316 }
317 }
318
319 pub fn remove_context(mut self) -> Self {
320 while let Self::Context { error, .. } = self {
321 self = *error;
322 }
323 self
324 }
325}
326
327pub fn map_err<E: Error>(error: E) -> PolarsError {
328 PolarsError::ComputeError(format!("{error}").into())
329}
330
331#[macro_export]
332macro_rules! polars_err {
333 ($variant:ident: $fmt:literal $(, $arg:expr)* $(,)?) => {
334 $crate::__private::must_use(
335 $crate::PolarsError::$variant(format!($fmt, $($arg),*).into())
336 )
337 };
338 ($variant:ident: $fmt:literal $(, $arg:expr)*, hint = $hint:literal) => {
339 $crate::__private::must_use(
340 $crate::PolarsError::$variant(format!(concat_str!($fmt, "\n\nHint: ", $hint), $($arg),*).into())
341 )
342 };
343 ($variant:ident: $err:expr $(,)?) => {
344 $crate::__private::must_use(
345 $crate::PolarsError::$variant($err.into())
346 )
347 };
348 (expr = $expr:expr, $variant:ident: $err:expr $(,)?) => {
349 $crate::__private::must_use(
350 $crate::PolarsError::$variant(
351 format!("{}\n\nError originated in expression: '{:?}'", $err, $expr).into()
352 )
353 )
354 };
355 (expr = $expr:expr, $variant:ident: $fmt:literal, $($arg:tt)+) => {
356 polars_err!(expr = $expr, $variant: format!($fmt, $($arg)+))
357 };
358 (op = $op:expr, got = $arg:expr, expected = $expected:expr) => {
359 $crate::polars_err!(
360 InvalidOperation: "{} operation not supported for dtype `{}` (expected: {})",
361 $op, $arg, $expected
362 )
363 };
364 (opq = $op:ident, got = $arg:expr, expected = $expected:expr) => {
365 $crate::polars_err!(
366 op = concat!("`", stringify!($op), "`"), got = $arg, expected = $expected
367 )
368 };
369 (un_impl = $op:ident) => {
370 $crate::polars_err!(
371 InvalidOperation: "{} operation is not implemented.", concat!("`", stringify!($op), "`")
372 )
373 };
374 (op = $op:expr, $arg:expr) => {
375 $crate::polars_err!(
376 InvalidOperation: "{} operation not supported for dtype `{}`", $op, $arg
377 )
378 };
379 (op = $op:expr, $arg:expr, hint = $hint:literal) => {
380 $crate::polars_err!(
381 InvalidOperation: "{} operation not supported for dtype `{}`\n\nHint: {}", $op, $arg, $hint
382 )
383 };
384 (op = $op:expr, $lhs:expr, $rhs:expr) => {
385 $crate::polars_err!(
386 InvalidOperation: "{} operation not supported for dtypes `{}` and `{}`", $op, $lhs, $rhs
387 )
388 };
389 (op = $op:expr, $arg1:expr, $arg2:expr, $arg3:expr) => {
390 $crate::polars_err!(
391 InvalidOperation: "{} operation not supported for dtypes `{}`, `{}` and `{}`", $op, $arg1, $arg2, $arg3
392 )
393 };
394 (opidx = $op:expr, idx = $idx:expr, $arg:expr) => {
395 $crate::polars_err!(
396 InvalidOperation: "`{}` operation not supported for dtype `{}` as argument {}", $op, $arg, $idx
397 )
398 };
399 (oos = $($tt:tt)+) => {
400 $crate::polars_err!(ComputeError: "out-of-spec: {}", $($tt)+)
401 };
402 (nyi = $($tt:tt)+) => {
403 $crate::polars_err!(ComputeError: "not yet implemented: {}", format!($($tt)+) )
404 };
405 (opq = $op:ident, $arg:expr) => {
406 $crate::polars_err!(op = concat!("`", stringify!($op), "`"), $arg)
407 };
408 (opq = $op:ident, $lhs:expr, $rhs:expr) => {
409 $crate::polars_err!(op = stringify!($op), $lhs, $rhs)
410 };
411 (bigidx, ctx = $ctx:expr, size = $size:expr) => {
412 $crate::polars_err!(ComputeError: "\
413{} produces {} rows which is more than maximum allowed pow(2, 32)-1 rows; \
414consider compiling with bigidx feature (pip install polars[rt64])",
415 $ctx,
416 $size,
417 )
418 };
419 (append) => {
420 polars_err!(SchemaMismatch: "cannot append series, data types don't match")
421 };
422 (extend) => {
423 polars_err!(SchemaMismatch: "cannot extend series, data types don't match")
424 };
425 (unpack) => {
426 polars_err!(SchemaMismatch: "cannot unpack series, data types don't match")
427 };
428 (not_in_enum,value=$value:expr,categories=$categories:expr) =>{
429 polars_err!(ComputeError: "value '{}' is not present in Enum: {:?}",$value,$categories)
430 };
431 (string_cache_mismatch) => {
432 polars_err!(StringCacheMismatch: r#"
433cannot compare categoricals coming from different sources, consider setting a global StringCache.
434
435Help: if you're using Python, this may look something like:
436
437 with pl.StringCache():
438 df1 = pl.DataFrame({'a': ['1', '2']}, schema={'a': pl.Categorical})
439 df2 = pl.DataFrame({'a': ['1', '3']}, schema={'a': pl.Categorical})
440 pl.concat([df1, df2])
441
442Alternatively, if the performance cost is acceptable, you could just set:
443
444 import polars as pl
445 pl.enable_string_cache()
446
447on startup."#.trim_start())
448 };
449 (duplicate = $name:expr) => {
450 $crate::polars_err!(Duplicate: "column with name '{}' has more than one occurrence", $name)
451 };
452 (duplicate_field = $name:expr) => {
453 $crate::polars_err!(Duplicate: "multiple fields with name '{}' found", $name)
454 };
455 (col_not_found = $name:expr) => {
456 $crate::polars_err!(ColumnNotFound: "{:?} not found", $name)
457 };
458 (mismatch, col=$name:expr, expected=$expected:expr, found=$found:expr) => {
459 $crate::polars_err!(
460 SchemaMismatch: "data type mismatch for column {}: expected: {}, found: {}",
461 $name,
462 $expected,
463 $found,
464 )
465 };
466 (oob = $idx:expr, $len:expr) => {
467 polars_err!(OutOfBounds: "index {} is out of bounds for sequence of length {}", $idx, $len)
468 };
469 (agg_len = $agg_len:expr, $groups_len:expr) => {
470 polars_err!(
471 ComputeError:
472 "returned aggregation is of different length: {} than the groups length: {}",
473 $agg_len, $groups_len
474 )
475 };
476 (parse_fmt_idk = $dtype:expr) => {
477 polars_err!(
478 ComputeError: "could not find an appropriate format to parse {}s, please define a format",
479 $dtype,
480 )
481 };
482 (length_mismatch = $operation:expr, $lhs:expr, $rhs:expr) => {
483 $crate::polars_err!(
484 ShapeMismatch: "arguments for `{}` have different lengths ({} != {})",
485 $operation, $lhs, $rhs
486 )
487 };
488 (length_mismatch = $operation:expr, $lhs:expr, $rhs:expr, argument = $argument:expr, argument_idx = $argument_idx:expr) => {
489 $crate::polars_err!(
490 ShapeMismatch: "argument {} called '{}' for `{}` have different lengths ({} != {})",
491 $argument_idx, $argument, $operation, $lhs, $rhs
492 )
493 };
494 (invalid_element_use) => {
495 $crate::polars_err!(InvalidOperation: "`element` is not allowed in this context")
496 };
497 (invalid_field_use) => {
498 $crate::polars_err!(InvalidOperation: "`field` is not allowed in this context")
499 };
500 (non_utf8_path) => {
501 $crate::polars_err!(ComputeError: "encountered non UTF-8 path characters")
502 };
503 (assertion_error = $objects:expr, $detail:expr, $lhs:expr, $rhs:expr) => {
504 $crate::polars_err!(
505 AssertionError: "{} are different ({})\n[left]: {}\n[right]: {}",
506 $objects, $detail, $lhs, $rhs
507 )
508 };
509 (to_datetime_tz_mismatch) => {
510 $crate::polars_err!(
511 ComputeError: "`strptime` / `to_datetime` was called with no format and no time zone, but a time zone is part of the data.\n\nThis was previously allowed but led to unpredictable and erroneous results. Give a format string, set a time zone or perform the operation eagerly on a Series instead of on an Expr."
512 )
513 };
514 (item_agg_count_not_one = $n:expr, allow_empty = $allow_empty:expr) => {
515 if $n == 0 && !$allow_empty {
516 polars_err!(ComputeError:
517 "aggregation 'item' expected a single value, got none"
518 )
519 } else if $n > 100 {
520 if $allow_empty {
521 polars_err!(ComputeError: "aggregation 'item' expected no or a single value, got 100+ values")
522 } else {
523 polars_err!(ComputeError: "aggregation 'item' expected a single value, got 100+ values")
524 }
525 } else if $n > 1 {
526 if $allow_empty {
527 polars_err!(ComputeError:
528 "aggregation 'item' expected no or a single value, got {} values", $n
529 )
530 } else {
531 polars_err!(ComputeError:
532 "aggregation 'item' expected a single value, got {} values", $n
533 )
534 }
535 } else {
536 unreachable!()
537 }
538 };
539}
540
541#[macro_export]
542macro_rules! polars_bail {
543 ($($tt:tt)+) => {
544 return Err($crate::polars_err!($($tt)+))
545 };
546}
547
548#[macro_export]
549macro_rules! polars_ensure {
550 ($cond:expr, $($tt:tt)+) => {
551 if !$cond {
552 $crate::polars_bail!($($tt)+);
553 }
554 };
555}
556
557#[inline]
558#[cold]
559#[must_use]
560pub fn to_compute_err(err: impl Display) -> PolarsError {
561 PolarsError::ComputeError(err.to_string().into())
562}
563
564#[macro_export]
565macro_rules! feature_gated {
566 ($($feature:literal);*, $content:expr) => {{
567 #[cfg(all($(feature = $feature),*))]
568 {
569 $content
570 }
571 #[cfg(not(all($(feature = $feature),*)))]
572 {
573 panic!("activate '{}' feature", concat!($($feature, ", "),*))
574 }
575 }};
576}
577
578#[doc(hidden)]
580pub mod __private {
581 #[doc(hidden)]
582 #[inline]
583 #[cold]
584 #[must_use]
585 pub fn must_use(error: crate::PolarsError) -> crate::PolarsError {
586 error
587 }
588}