65832db6d8
* feat(python): migrate Python bindings from ctypes to PyO3 - Add native PyO3 extension in crates/fff-python - Replace ctypes wrapper with maturin-built package in packages/fff-python - Expose FileFinder, search/glob/directory/mixed/grep APIs and result types - Use PyPI package name fff-python (import name remains fff) - Update workspace Cargo.toml/Cargo.lock and .gitignore for Python artifacts * ci: add Python CI workflow and release wheel builds - Add .github/workflows/python.yml to test bindings on Ubuntu/macOS/Windows - Extend release.yaml with Python wheel builds (x86_64/aarch64) and sdist - Add optional PyPI publish job using trusted publishing * fix(python): address review feedback and release CI Rust bindings: - Release GIL during heavy search/grep operations via py.allow_threads - Add MixedFileItem/MixedDirItem::from_core to avoid double-cloning - Return PyDict directly from health_check and drop serde_json dependency - Call destroy() in __exit__ so the context manager releases resources - Add mode parameter to multi_grep for parity with grep - Preserve cache budget overrides across reindex() - Rename combo_boost param to match FuzzySearchOptions field Docs/tests: - Update Python test for dict-returning health_check - Add Python bindings section to main README CI: - Fix pypi-publish job to depend on build-python/build-python-sdist instead of release, making the workflow_dispatch checkbox functional * fix(python): sync release versions and expand tests * refactor(python): split lib.rs and improve Pythonic API - Split crates/fff-python/src/lib.rs into modules: - types.rs: all pyclass result types - finder.rs: FileFinder implementation - conversions.rs: From/core conversions - Make API more Pythonic: - FileFinder now accepts pathlib.Path / os.PathLike for base_path and reindex - Add close() alias for destroy() - grep/multi_grep now raise FFFException for invalid modes - GrepResult gains has_more property and next_cursor() method - Add type stubs: - packages/fff-python/src/fff/__init__.pyi - packages/fff-python/src/fff/py.typed - Add __repr__ implementations for all exposed pyclasses - Expand Python tests for pathlib, close(), reprs, invalid mode, and cursor pagination * fix(python): align type stubs and runtime API * refactor(python): polish binding API and GIL handling Make the Python binding API more idiomatic before merge: replace getter-style methods with properties, keep close() as the single explicit shutdown API, add container semantics for result objects, and tighten type stubs with Literal/Sequence/PathLike support. Also reduce Rust binding duplication with shared option/result helpers and release the Python GIL around blocking filesystem, git, and query-history operations. * feat(python): async wait_for_scan with blocking variant * fix(python): consistent frecency type and combo defaults matching node * fix(python): health_check defaults to indexed path and reports cwd errors; expand readme * chore(python): rename pypi distribution to fff-search * chore(python): align version to 0.9.4 for unified release * chore(release): bump python version with sed instead of inline python * refactor(python): use From trait for core type conversions * chore: do not run full prebuild of python wheels on PR --------- Co-authored-by: Dmitriy Kovalenko <dmtr.kovalenko@outlook.com>
44 lines
1.3 KiB
Python
44 lines
1.3 KiB
Python
"""Standalone example of using fff Python bindings."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import sys
|
|
import time
|
|
|
|
from fff import FileFinder
|
|
|
|
|
|
def main() -> int:
|
|
base_path = sys.argv[1] if len(sys.argv) > 1 else "."
|
|
|
|
print(f"Indexing {base_path}...")
|
|
start = time.time()
|
|
with FileFinder(base_path, watch=False) as finder:
|
|
print(f"Created in {time.time() - start:.2f}s")
|
|
|
|
print("Waiting for scan...")
|
|
finder.wait_for_scan_blocking(timeout_ms=30000)
|
|
progress = finder.scan_progress
|
|
print(f"Indexed {progress.scanned_files_count} files")
|
|
|
|
print("\nFuzzy file search for 'main':")
|
|
result = finder.search("main", page_size=5)
|
|
for item, score in zip(result.items, result.scores):
|
|
print(f" {item.relative_path:<50} score={score.total}")
|
|
|
|
print("\nGlob search '*.py':")
|
|
result = finder.glob("*.py", page_size=5)
|
|
for item in result.items:
|
|
print(f" {item.relative_path}")
|
|
|
|
print("\nGrep for 'def ':")
|
|
result = finder.grep("def ", page_limit=5)
|
|
for match in result.items:
|
|
print(f" {match.relative_path}:{match.line_number}: {match.line_content.strip()}")
|
|
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|