pola-rs/polars · error · IsADirectoryError
expected a file path; {path!r} is a directory
Error message
expected a file path; {path!r} is a directory What it means
IsADirectoryError from normalize_filepath (py-polars/src/polars/_utils/various.py:255-265). Most polars IO entry points (read_csv, scan_parquet, read_ipc, etc.) pass the user's path through normalize_filepath with check_not_directory=True; if the expanded path exists on disk and is a directory, polars refuses it up front instead of letting the OS produce a confusing read failure. Multi-file directory reads are intentionally not implicit: use an explicit glob.
Source
Thrown at py-polars/src/polars/_utils/various.py:264
def arrlen(obj: Any) -> int | None:
"""Return length of (non-string/dict) sequence; returns None for non-sequences."""
try:
return None if isinstance(obj, (str, bytes, dict)) else len(obj)
except TypeError:
return None
def normalize_filepath(path: str | Path, *, check_not_directory: bool = True) -> str:
"""Create a string path, expanding the home directory if present."""
# don't use pathlib here as it modifies slashes (s3:// -> s3:/)
path = os.path.expanduser(path) # noqa: PTH111
if (
check_not_directory
and os.path.exists(path) # noqa: PTH110
and os.path.isdir(path) # noqa: PTH112
):
msg = f"expected a file path; {path!r} is a directory"
raise IsADirectoryError(msg)
return path
def parse_version(version: Sequence[str | int]) -> tuple[int, ...]:
"""Simple version parser; split into a tuple of ints for comparison."""
if isinstance(version, str):
version = version.split(".")
return tuple(int(re.sub(r"\D", "", str(v))) for v in version)
def ordered_unique(values: Sequence[Any]) -> list[Any]:
"""Return unique list of sequence values, maintaining their order of appearance."""
seen: set[Any] = set()
add_ = seen.add
return [v for v in values if not (v in seen or add_(v))]
def deduplicate_names(names: Iterable[str]) -> list[str]:View on GitHub (pinned to df599052da)
Solutions
- Pass a file path or an explicit glob pattern, e.g. pl.scan_csv('data/*.csv')
- If you want one file per call, iterate: [pl.read_csv(p) for p in sorted(Path('data').glob('*.csv'))]
- Fix the upstream path variable that accidentally holds a directory
- For single-file inputs, validate with Path(p).is_file() before calling the reader
Example fix
# before
lf = pl.scan_parquet('partitioned_table/') # IsADirectoryError
# after
lf = pl.scan_parquet('partitioned_table/**/*.parquet') Defensive patterns
Strategy: validation
Validate before calling
from pathlib import Path
def as_readable_path(p: str | Path) -> str:
p = Path(p).expanduser()
if p.is_dir():
raise IsADirectoryError(f'{p} is a directory; pass a file or glob')
return str(p) Try / catch
try:
df = pl.read_parquet(path)
except IsADirectoryError:
df = pl.read_parquet(str(Path(path) / '**' / '*.parquet')) Prevention
- Validate Path(p).is_file() (or a glob) before IO calls on user-supplied paths
- Use explicit globs ('dir/*.csv') for multi-file reads
- Centralize path normalization in one helper for configurable inputs
When it happens
Trigger: pl.scan_csv('data/') or pl.read_parquet('my_folder') where the argument is a directory; also passing a Path object that points at a directory. Expansion of ~ happens first, so '~/data' that is a directory also raises.
Common situations: Assuming read_csv on a directory reads all contained CSVs (pandas-like glob assumption); configurable path inputs that resolve to a folder in one environment and a file in another; download scripts writing to a directory then passing it back as a file path.
Related errors
- the `columns` argument should contain a list of all integers
- `columns` arg should only have unique values, got {columns!r
- `fsspec` is required for `storage_options` argument
- empty data from {context}{hint}
- file {source.name!r} must be opened in binary mode
AI-assisted analysis of pola-rs/polars@df599052da (2026-08-16).
Data as JSON: /api/errors/56d7558a7d31c320.
Report an issue: GitHub.