watchftp
Async, polling-based watcher for FTP/SFTP endpoints with optional Redis-backed persistence and change streams.
Overview
watchftp scans remote trees using protocol-native metadata (MLSD for FTP, AsyncSSH for SFTP), fingerprints directories, and emits batched change events (create/modify/delete/heuristic move). The engine is fully async, tuned for large trees (250k+ files), and can persist its state plus event stream in Redis for multi-consumer durability.
Key capabilities:
- Async core: aioftp + AsyncSSH connectors with bounded concurrency.
- Pattern-aware traversal: glob/regex includes/excludes compiled via
wcmatchfallback. - Directory fingerprints: Merkle-style digests to skip cold subtrees and detect changes efficiently.
- Redis integration: Optional persistence and Streams; tenants are fully namespaced (
wf:{tenant}:{server}:...). Defaults toredis://127.0.0.1:6379/0when Redis streaming is enabled without a URL so local testing “just works”. - Config via Pydantic: Load settings from environment variables using
pydantic-settings, including scale presets, watch-root limits (with-1meaning unlimited), and security toggles.
Getting Started
Requirements
- Python 3.12+
- FTP/SFTP endpoints to monitor
- Optional Redis (local development uses
redis://127.0.0.1:6379/0)
Install with uv (recommended) or pip:
uv pip install -e .
# or
pip install -e .
Quick start
import asyncio
from watchftp import WatchConfig, WatchPath, Watcher, tune_for_scale
cfg = WatchConfig(
tenant_id="demo-tenant",
protocol="sftp",
host="sftp.internal",
username="svc",
private_key="~/.ssh/id_ed25519",
watch_paths=[WatchPath(root="/incoming", include=["**/*.csv"], exclude=["**/tmp/**"])]
)
tune_for_scale(cfg, "medium")
async def main():
async with Watcher(cfg) as watcher:
async for batch in watcher.watch():
for event in batch:
print(event.type, event.path)
asyncio.run(main())
Enable Redis streams and persistence by setting cfg.event_bus = "redis_stream" (or WATCHFTP_EVENT_BUS=redis_stream). If no redis_url is provided, the watcher will connect to redis://127.0.0.1:6379/0 automatically for local development.
Documentation
We publish docs with MkDocs Material. To preview locally:
uv pip install mkdocs mkdocs-material
mkdocs serve
Changes are automatically built and deployed to GitHub Pages whenever pyproject.toml's version is bumped on main (see .github/workflows/docs.yml).
Configuration
The main knobs live on WatchConfig (Pydantic model):
tenant_id: required for namespacing Redis state.watch_paths: list ofWatchPathroots with include/exclude patterns (glob or regex).max_watch_roots: cap per process (-1for unlimited).- Concurrency knobs (
poll_interval,max_concurrency, per-protocol limits). - Security toggles (
ftps_verify_cert,sftp_known_hosts, allowed ciphers). - Persistence settings (
redis_url,redis_ttl_seconds,event_bus).
Environment variables are loaded via WatcherSettings with the prefix WATCHFTP_ (e.g., WATCHFTP_TENANT, WATCHFTP_REDIS_URL). Nested values use __ (double underscore) separators.
configure_logging(level="INFO", structured=True): optional helper to emit JSON logs vialogging. Call once in your app (from watchftp import configure_logging).ensure_metrics_server(port=9464): starts the Prometheus HTTP endpoint (usesprometheus-client).
Example:
from watchftp import configure_logging, ensure_metrics_server
configure_logging("INFO")
ensure_metrics_server()
Project Roadmap
Testing
-
Unit tests (Redis + helpers):
python -m unittest -
Integration tests (FTP/SFTP against pyftpdlib + AsyncSSH) require Python 3.12+ and can be run with
pytest -m integration. They start ephemeral servers locally. -
M0 (current): Config scaffolding, scheduler skeleton, Redis index + stream sink, unit tests.
-
M1: Real FTP/SFTP traversal, diff engine polish, adaptive scheduler knobs.
-
M2: Advanced Redis-backed recovery, rename heuristics, metrics exposure, docs/examples.
See docs/project.md for the full PRD, architecture, and sizing matrix.
Metadata
Release files for watchftp 0.0.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| watchftp-0.0.1.tar.gz | 19.3 kB | Without provenance | Details for watchftp-0.0.1.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| watchftp-0.0.1-py3-none-any.whl | Python 3 | none | any | Details for watchftp-0.0.1-py3-none-any.whl |
Total release size: 40.9 kB
Release files / watchftp-0.0.1.tar.gz
| Download URL | watchftp-0.0.1.tar.gz |
|---|---|
| Size | 19.3 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
a64d5bc683431ded92f2baef6ff66823152c5e17a5a4f1855d34867e8f2f3eeb
|
|
BLAKE2b-256 checksum How to use checksums |
6db45d83f0044e5c1e4c635c8dff13bfb7bb2ca408915206c5946e06572c1d97
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.9.8
|
Release files / watchftp-0.0.1-py3-none-any.whl
| Download URL | watchftp-0.0.1-py3-none-any.whl |
|---|---|
| Size | 21.6 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
bcdf64499538a58f44e4c545dc7288ab040ab1457fdee3a82ea8665a11a34ea2
|
|
BLAKE2b-256 checksum How to use checksums |
346ef941846eaab597218b9dc910a66f952ee3e6d57ec39a471dc9fa6e358855
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.9.8
|
Security for watchftp 0.0.1
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.