pola-rs/polars · error · PolarsError::IO

{}: {path}

Error message

{}: {path}

What it means

This error wraps an I/O error (e.g. file-not-found, permission denied) with the full path involved, preserving the original io::ErrorKind. Polars attaches the path so the message stays readable (long paths are truncated in the message, full path available via PathIoError). It is a context-adding wrapper, not a failure class of its own — the underlying kind (NotFound, PermissionDenied, etc.) determines what went wrong.

Source

Thrown at crates/polars-utils/src/io.rs:52

                self.source
            )
        } else {
            write!(f, "{}: {path}", self.source)
        }
    }
}

impl std::error::Error for PathIoError {
    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
        Some(&self.source)
    }
}

/// Attaches `path` to `err`, keeping its [`io::ErrorKind`].
///
/// The path is available in full through [`PathIoError`], and truncated in the message.
pub fn _limit_path_len_io_err(path: &Path, err: io::Error) -> PolarsError {
    io::Error::new(
        err.kind(),
        PathIoError {
            path: path.to_path_buf(),
            source: err,
        },
    )
    .into()
}

pub fn open_file(path: &Path) -> PolarsResult<File> {
    File::open(path).map_err(|err| _limit_path_len_io_err(path, err))
}

pub fn open_file_write(path: &Path) -> PolarsResult<File> {
    std::fs::OpenOptions::new()
        .write(true)
        .create(true)
        .truncate(true)

View on GitHub (pinned to 68506541d2)

Solutions

  1. Verify the path exists and is spelled correctly: run `ls <path>` (or the equivalent for your storage backend) before calling the Polars API.
  2. Use absolute paths to avoid working-directory surprises, and confirm the path prefix matches the IO plugin/cloud scheme you intend (s3://, gs://, file://).
  3. Check permissions on the file or parent directory, and on any directories mkdir_recursive will create.
  4. Inspect the wrapped PathIoError / err.kind() in the message to distinguish NotFound vs PermissionDenied vs other OS errors and fix accordingly.

Example fix

// before
lf = pl.scan_csv("mydat.csv")  # FileNotFoundError: path attached
// after
from pathlib import Path
p = Path("data/mydat.csv")
assert p.exists(), f"missing input: {p}"
lf = pl.scan_csv(str(p))
Defensive patterns

Strategy: try-catch

Validate before calling

from pathlib import Path
def ensure_readable(p):
    path = Path(p)
    if not path.exists():
        raise FileNotFoundError(p)
    if not path.is_file():
        raise IsADirectoryError(p)
    if not os.access(path, os.R_OK):
        raise PermissionError(p)
    return str(path.resolve())

Try / catch

try:
    lf = pl.scan_csv(path)
except (FileNotFoundError, PermissionError, OSError) as e:
    kind = getattr(getattr(e, '__cause__', e), 'errno', None)
    log.error(f"IO failed for path: {e}")
    raise

Prevention

When it happens

Trigger: Any Polars operation that resolves a file path and fails at the OS level: reading/scanning a file (expand_path_cloud, open_blocking, open_file), memory-mapping a file (try_new_mmap_from_path), or creating directories recursively (mkdir_recursive, tokio_mkdir_recursive) when the path does not exist, is misspelled, or lacks permissions.

Common situations: Typos in file paths, reading files that were moved or deleted between existence check and open, relative paths resolved from an unexpected working directory, cloud-storage paths passed without the right prefix/credentials, insufficient permissions on the target path or parent directory when creating directories.

Understand the failure class

Background: "File not found" and ENOENT errors: why libraries can't find a file that should exist — this error's family across 50 libraries.

Related errors


AI-assisted analysis of pola-rs/polars@68506541d2 (2026-09-10). Data as JSON: /api/errors/a0449cd7f08b0ccc. Report an issue: GitHub.