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