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