A lightweight, asyncio-friendly, cross-platform file watching library.
Project description
🚀 yyds-fswatch
yyds-fswatch is a lightweight, asyncio-friendly, and developer-friendly file system watching library for Python. It offers a compact alternative to observer/handler-style APIs while keeping platform fallbacks explicit and observable.
🌟 Core Advantages (vs Watchdog)
| Feature | yyds-fswatch (This Library) |
watchdog |
|---|---|---|
| API Usability | 4-level progressive API (Decorators / Callbacks / Sync Iter / Async Iter) | Verbose Observer + Handler class inheritance system |
| Async Integration | Native coroutine support (async with and async for) |
Requires manual threadpools or loop.call_soon_threadsafe bridging |
| Linux Performance | Zero additional threads (ctypes non-blocking inotify + loop.add_reader) |
Starts an independent Python blocking thread for each Emitter |
| Atomic Save Support | File watches monitor the parent directory and normalize inbound replacement moves to target-file events | Behavior depends on the observer and handler configuration |
| Debounce & Coalesce | Built-in 50ms sliding window state machine | No debouncing; saving one file triggers dozens of redundant modified events |
| GitIgnore Pruning | Dynamically reloads nested .gitignore files and supports the commonly used glob subset |
Requires application-level filtering |
| Rename Sync | Uses Linux cookies, Windows packets, FSEvents heuristics, and inode pairing in Polling | Backend-dependent |
| Overflow Resilience | Emits overflow; Linux rebuilds its recursive watch tree after kernel overflow |
Backend-dependent |
| Root Self-Healing | Restarts the backend when watched roots disappear or reappear | Usually requires observer recreation |
📦 Installation
To install yyds-fswatch via pip:
pip install -U yyds-fswatch
Or install from source inside the project directory:
pip install .
🛠️ Progressive API Usage Examples
yyds-fswatch provides four API levels to suit different integration needs.
1. Level 1: Decorator Mode
The most intuitive mode, supporting both synchronous and asynchronous callback functions.
from yyds_fswatch import FSWatcher
w = FSWatcher("./watch_dir", recursive=True, debounce=0.05)
@w.on("created")
def on_created(event):
print(f"🔥 [CREATED] {'Directory' if event.is_directory else 'File'}: {event.src_path}")
@w.on("modified")
def on_modified(event):
print(f"⚡ [MODIFIED]: {event.src_path}")
@w.on("deleted")
def on_deleted(event):
print(f"🗑️ [DELETED]: {event.src_path}")
@w.on("moved")
def on_moved(event):
print(f"📦 [MOVED]: {event.src_path} -> {event.dest_path}")
# Start watching (blocks current thread; run in a separate thread if non-blocking is needed)
# Blocks until Ctrl+C or until another thread calls w.stop().
w.start()
2. Level 2: One-Liner Callback
Ideal for quick scripts and debugging.
from yyds_fswatch import watch
# Blocks and runs callback on any file changes
watch("./watch_dir", on_change=lambda event: print(f"⚡ Event: {event.event_type} on {event.src_path}"))
3. Level 3: Sync Standard Iterator
Uses the with context manager to process file events sequentially like a generator.
from yyds_fswatch import FSWatcher
# Starts a background thread within the context and auto-cleans resources upon exit
with FSWatcher("./watch_dir", debounce=0.05) as w:
for event in w:
print(f"🔄 [Sync Iter] {event.event_type.upper()}: {event.src_path}")
if event.event_type == "deleted" and "stop_trigger" in event.src_path:
break # Exiting the loop will cleanly terminate the watcher
4. Level 4: Async Native Iterator - ✨ Recommended
Integrates with modern async frameworks (FastAPI, Sanic, Tornado). Linux uses the caller's asyncio loop directly; Windows/macOS native backends use an OS-waiting helper thread.
import asyncio
from yyds_fswatch import FSWatcher
async def main():
# Pure async context manager and iterator
async with FSWatcher("./watch_dir", debounce=0.05) as w:
async for event in w:
print(f"👀 [Async Iter] {event.event_type.upper()}: {event.src_path}")
asyncio.run(main())
Lifecycle, backpressure, and backend controls
import threading
from yyds_fswatch import FSWatcher
w = FSWatcher(
"./watch_dir",
max_queue_size=4096, # 0 opts into an unbounded queue
backend="auto", # auto | native | polling
recovery_interval=2.0,
)
thread = threading.Thread(target=w.start)
thread.start()
w.wait_until_ready(timeout=5)
# ...later
w.stop() # public, idempotent, and thread-safe
thread.join()
Use target_type="file" when a watched file does not exist yet. Set ignore_common_dirs=False when changes inside dist, build, node_modules, and other common generated directories must be reported.
🛡️ Deep Technical Optimization Details
- Atomic Save Normalization: For single-file watches (e.g.
data.json), the system monitors the parent directory. A temporary file moved onto an existing target is emitted asmodifiedon the target path. - Cached Path Filtering: Absolute-root prefix checks and a bounded 4,096-entry decision cache reduce repeated filtering work. No unverified throughput number is claimed; benchmark on the intended directory tree and platform.
- Lifecycle & Memory Controls: Context managers and the public
stop()method release backend resources. User-space queues are bounded to 4,096 events by default and emitoverflowwhen consumers fall behind. - System Buffer Overflow Handling: OS queue saturation emits an
overflowevent. Linux also recreates its inotify instance and recursive watch tree so missed directory registrations do not remain permanent. - Hierarchical Nested
.gitignoreSupport: Nested files are invalidated when they change. The built-in parser supports common*,?,**, anchoring, directory, and negation patterns; it is intentionally documented as a practical subset rather than byte-for-byte Git equivalence. - Self-Healing Root Recovery: If a watched root directory is deleted or unmounted, the watcher pauses native OS watches and falls back to a low-overhead retry loop. Monitoring is automatically re-established the moment the path reappears.
📈 Resource and Long-Running Process Controls
yyds-fswatch includes controls useful for long-running processes. Production adoption should still be validated on every target OS and file-system type:
- Bounded queues and cache: Event queues and the
.gitignoredecision cache default to4,096entries. Backend descriptor maps naturally scale with the number of watched directories. - Low idle overhead: Native drivers wait on OS facilities. A lightweight lifecycle heartbeat supports prompt cross-thread shutdown, a configurable root-health probe runs every two seconds by default, and the Polling backend scans at its configured interval.
- Auto-Expiring State Machine: Temporary states (such as rename cookie matching and debouncing queues) auto-expire and purge themselves within 50ms, maintaining a baseline size of zero when idle.
- Watches Pruning & Handle Safety: Subdirectory watches are pruned dynamically at startup based on
.gitignorerules (avoidingENOSPCdescriptor limits). System handles/file descriptors are synchronized dynamically when folders are deleted, and are safely closed upon stop.
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file yyds_fswatch-0.2.0.tar.gz.
File metadata
- Download URL: yyds_fswatch-0.2.0.tar.gz
- Upload date:
- Size: 41.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.11
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a32a681d91005fa27ac7f0ceae2d84a2c1c4527140152374bc52ba3cd8693e62
|
|
| MD5 |
b5f147e29545f551fb23ceea1b86b25e
|
|
| BLAKE2b-256 |
778f6ba0348e6ddf22861861f75bb8eaff739f6c6ab25297a8c93768221cbe90
|
File details
Details for the file yyds_fswatch-0.2.0-py3-none-any.whl.
File metadata
- Download URL: yyds_fswatch-0.2.0-py3-none-any.whl
- Upload date:
- Size: 28.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.11
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
4e68a7729e149524c26eb89459a7d676993c586ce13b50d12b75769991ac0206
|
|
| MD5 |
e8c426747515dc41cd4615f73f36b622
|
|
| BLAKE2b-256 |
1c22d19b6a5253dad22c1cda9fc86d74794e186946c46b77920fcc596e80a2d2
|