Skip to content

Parallel walkers, abi3 wheels, Python 3.9 CLI fix, limit/threads options - #1

Merged
lperezmo merged 4 commits into
masterfrom
perf/parallel-walkers-and-fixes
Jul 3, 2026
Merged

Parallel walkers, abi3 wheels, Python 3.9 CLI fix, limit/threads options#1
lperezmo merged 4 commits into
masterfrom
perf/parallel-walkers-and-fixes

Conversation

@lperezmo

@lperezmo lperezmo commented Jul 3, 2026

Copy link
Copy Markdown
Owner

Summary

Full fix-and-optimize pass covering the issues found in review plus the performance work.

Fixes

  • CLI crashed on Python 3.9: PEP 604 annotation in cli.py failed at import. Fixed with from __future__ import annotations; CI now runs the CLI in every matrix cell so this class of bug cannot recur.
  • Broken wheel matrix: 0.5.0 shipped Windows wheels for cp312 only and macOS for cp314 only. Now built as abi3-py39: one wheel per platform covers Python 3.9+, including future versions.
  • panic=abort removed: a Rust panic no longer kills the host Python process; PyO3 converts it to a catchable exception.
  • Consistency: unreadable metadata excludes a file whenever a size/time filter is active (previously inconsistent across functions); index stem collisions are deterministic (smallest path wins); walk with filters returns only matching files instead of interleaving every directory; list_dir is sorted; skip_hidden honors the Windows hidden attribute; CLI exits cleanly on | head and Ctrl+C.

Performance

walk/find/glob/index move onto the ignore crate's parallel walker (same engine as disk_usage since 0.5.0), dropping jwalk. Metadata reads run on N threads and come from the directory read itself where the platform provides it. glob starts at the pattern's literal prefix. Name/ext filters no longer allocate when unset.

Benchmark on a 442k-file tree (Windows, warm cache):

Operation 0.5.0 this PR
walk (no filters) 19,099 ms 380 ms
walk ext filter 6,299 ms 340 ms
find names+ext 1,884 ms 314 ms
find min_size_mb=10 17,343 ms 307 ms
glob **/*.py 561 ms 346 ms
find limit=1 n/a 1.5 ms

New API

  • find(..., limit=N): stop as soon as N matches are found (CLI --limit)
  • threads=N on all recursive functions (CLI --threads), useful on network drives

Testing

  • 87/87 tests pass on Python 3.9 and 3.13 (local Windows, abi3 wheel)
  • Result counts verified identical to 0.5.0 for unfiltered walks
  • New tests: limit, threads, dirs-excluded-when-filtered, index collision determinism, sorted list_dir

lperezmo added 4 commits July 2, 2026 18:46
…output writes

PEP 604 annotations in cli.py made the console script crash at import
on Python 3.9 even though the package claims to support it. Add
from __future__ import annotations.

Exit cleanly on BrokenPipeError (e.g. piping to head) and on
KeyboardInterrupt instead of printing an error or a traceback.

Write output lines in a single buffered call instead of one print per
line, and only pretty-print JSON when stdout is a terminal.
The release workflows only passed an interpreter list to the Linux
builds, so Windows wheels were published for a single Python version
(cp312 for 0.5.0) and macOS for whatever the runner had (cp314).
Everyone else silently fell back to the sdist and needed a Rust
toolchain. Build with pyo3 abi3-py39 instead: one wheel per platform
covers Python 3.9 and up, including future versions.

Also drop panic=abort from the release profile. In a cdylib loaded
into a host interpreter it turned any Rust panic into a hard abort of
the whole Python process; without it PyO3 converts panics into a
catchable PanicException.

Bump the trove classifier from Alpha to Beta.
Move walk, find, glob and index onto the ignore crate's parallel
walker, the same engine disk_usage already uses, and drop the jwalk
dependency. Filtering and metadata reads now run on N worker threads,
and metadata comes from the directory read itself where the platform
provides it (free on Windows) instead of a second stat per file.

On a 442k-file tree on Windows: walk 19.1s -> 0.38s, find by size
17.3s -> 0.31s, find by name 1.9s -> 0.31s.

New options: find(limit=N) stops the search as soon as N matches are
found (a single-file lookup on the same tree returns in ~1.5ms), and
every recursive function takes threads=N to tune walker parallelism,
which helps on network drives.

Also in this change:
- glob starts walking at the pattern's literal directory prefix
  (src/**/*.py no longer scans the whole tree)
- walk returns only matching files when any filter is active instead
  of interleaving every directory with the matches
- index resolves stem collisions deterministically by keeping the
  lexicographically smallest path
- files whose metadata cannot be read are excluded consistently when
  a size or time filter is active
- list_dir results are sorted by name, and skip_hidden honors the
  Windows hidden attribute everywhere
- name and extension filters no longer allocate per file when unset,
  which speeds up unfiltered disk_usage
README: document limit and threads, the behavior notes (hidden files,
creation time on Linux, unreadable metadata, result ordering, index
collisions), update the performance section, and drop the downloads
badge (it permanently rendered as rate limited).

CI now runs the installed console script and python -m pyofiles across
every matrix cell, which would have caught the Python 3.9 CLI import
crash.
@lperezmo
lperezmo merged commit 9483b6d into master Jul 3, 2026
10 checks passed
@lperezmo
lperezmo deleted the perf/parallel-walkers-and-fixes branch July 3, 2026 01:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant