Releases: plutopulp/rheo
v0.7.0: Manager-level retries & Python 3.11+
🚨 Breaking Changes
- Minimum Python version is now 3.11+: Type checking and runtime features (e.g.,
StrEnum,asyncio.Timeout,typing.Unpack) require Python 3.11 or later. CI and metadata updated accordingly.
✨ What's New
- Manager-level retry handler configuration:
DownloadManagernow acceptsretry_handlerand threads it throughWorkerPooland workers. Retry events flow through the shared emitter. Integration tests cover retry counts, custom policies (e.g., 404 as transient), and default no-retry behaviour. - New example:
examples/07_retry_handling.pydemonstrates basic retries, custom policies, event subscription for retries, and no-retry behaviour.
🔧 Internal Improvements
- Docs and badges: Reordered badges, added "commits since release" badge, refreshed retry docs to show manager-level configuration and custom policies.
- Tooling: CI matrix and mypy target aligned to Python 3.11–3.14; packaging metadata updated.
- Cleanup: Removed legacy
src/rheo/main.pydemo script and unusedtests/fixtures/data. Dropped deadFileConfig.max_retriesfield.
📝 Full Details
See the complete changelog: CHANGELOG.md
📦 Installation
pip install --upgrade rheopy🙏 Thank You
Thanks for using Rheo! If you spot any issues, please open an issue on GitHub.
v0.6.0: Cancellation & HTTP Client Abstraction
🚨 Breaking Changes
- Event subscription API:
manager.on()now returns aSubscriptionhandle withunsubscribe(). The previousmanager.off()is removed. Event names are typed viaDownloadEventType(StrEnum). - HTTP client abstraction:
DownloadManager,WorkerPool, andDownloadWorkernow depend onBaseHttpClient(defaultAiohttpClient) instead of rawaiohttp.ClientSession. Custom clients must implementBaseHttpClient.
Migration examples:
# Before
sub = manager.on("download.completed", handler)
manager.off("download.completed", handler)
# After
sub = manager.on(DownloadEventType.COMPLETED, handler)
sub.unsubscribe()# Custom client injection
client = AiohttpClient() # or your BaseHttpClient impl
async with DownloadManager(http_client=client) as manager:
...✨ What's New
- Selective cancellation:
manager.cancel(download_id) -> CancelResultwith cooperative cancellation for queued downloads and task-level cancellation for in-progress downloads.DownloadCancelledEventnow includescancelled_from(QUEUED vs IN_PROGRESS). - Cancellation observability:
DownloadStatsnow reportscancelledcount viamanager.stats. - File exists policy:
DestinationResolver+FileExistsPolicycentralise file-exists handling withdefault_file_exists_strategy; workers emit skips when policy returnsNone. - Typed events:
DownloadEventTypeenum for event names,Subscriptionhandle returned frommanager.on(). - Benchmarks: Added minimal
benchmarks/suite (pytest-benchmark) with HTTP fixture server for throughput profiling.
🔧 Internal Improvements
- HTTP client layer: Introduced
BaseHttpClientABC,AiohttpClientimplementation, SSL factory helpers, and lifecycle management; manager only opens clients it owns. - Exception hierarchy: New
RheoErrorbase;InfrastructureError→HttpClientError→ClientNotInitialisedError;DownloadManagerErrornow derives fromRheoError. - CI hardening: Poetry 2.x install flags (
--all-groups), reordered caching, and version diagnostics. - Docs & examples: Updated to demonstrate
DownloadEventType,Subscription, and selective cancellation usage.
📝 Full Details
See the complete changelog: CHANGELOG.md
📦 Installation
pip install --upgrade rheopy🙏 Thank You
Thanks for using Rheo! If you encounter any issues with the migration, please open an issue.
v0.5.0: Event System Overhaul
🚨 Breaking Changes
This release overhauls the event system for a cleaner, more intuitive API.
Event Subscription:
- Subscribe to events via
manager.on()instead oftracker.on() - Tracker is now observe-only (no longer emits events)
Event Names:
- All events renamed from
worker.*todownload.*namespace - Example:
worker.started→download.started
Migration Example:
# Before (v0.4.0)
tracker.on("download.completed", handler)
# After (v0.5.0)
manager.on("download.completed", handler)
# Wildcard subscription for all events
manager.on("*", lambda e: print(e.event_type))✨ What's New
Manager as Event Facade (#74):
manager.on(event, handler)- Subscribe to download eventsmanager.off(event, handler)- Unsubscribe from eventsmanager.on("*", handler)- Receive all eventsmanager.get_download_info(id)- Query download statemanager.stats- Get aggregate statistics
New Lifecycle Events (#69, #70, #71):
download.queued- When added to queuedownload.skipped- When file exists and strategy is SKIPdownload.cancelled- When download is cancelleddownload.validating- When hash validation startsdownload.retrying- Before retry attempt
Embedded Validation Results (#70):
download.completedanddownload.failedevents includevalidationfield- Self-contained
ValidationResultwithis_valid, expected/calculated hash
New Examples (#75):
04_progress_display.py- Real-time progress bar with speed/ETA05_event_logging.py- Lifecycle debugging with wildcard subscription06_batch_summary.py- Batch download with summary report
CLI Progress Restored:
- Real-time progress bar with speed and ETA during downloads
- Detailed validation and error output on completion
🔧 Internal Improvements
Pydantic Events (#66):
- All events migrated to Pydantic models with validation
- Immutable, UTC-timestamped, type-safe
Centralised Event Wiring (#73):
- Manager owns all event wiring
- WorkerPool is tracker-agnostic
- Cleaner separation of concerns
Shared Emitter Architecture (#74):
- Single emitter owned by Manager, shared across queue/pool/workers
- Unified subscription point
📝 Full Details
See the complete changelog: CHANGELOG.md
📦 Installation
pip install --upgrade rheopy🙏 Thank You
Thanks for using Rheo! If you encounter any issues with the migration, please open an issue.
v0.4.0: File Safety & Cancellation Handling
Release Title
v0.4.0: File Safety & Cancellation Handling
Release Notes
🚨 Breaking Changes
This release introduces safer defaults that may require code changes.
File Exists Handling:
- Existing files are now SKIPped by default (previously silently overwritten)
- New
FileExistsStrategyenum:SKIP,OVERWRITE,ERROR
Context Manager Safety:
PendingDownloadsErrorraised if exitingasync withwithout handling downloads- Must call
wait_until_complete()orclose()explicitly
Migration Example:
# Before (v0.3.0)
async with DownloadManager(download_dir=Path("./downloads")) as manager:
await manager.add(files)
# Silent cancellation, silent overwrite
# After (v0.4.0)
async with DownloadManager(download_dir=Path("./downloads")) as manager:
await manager.add(files)
await manager.wait_until_complete() # Required
# To restore overwrite behavior:
async with DownloadManager(
download_dir=Path("./downloads"),
file_exists_strategy=FileExistsStrategy.OVERWRITE,
) as manager:
...✨ What's New
FileExistsStrategy (#63):
SKIP(default): Skip download if file exists, log and continueOVERWRITE: Replace existing file with new downloadERROR: RaiseFileExistsErrorif file exists- Configurable at manager level or per-file via
FileConfig.file_exists_strategy
PendingDownloadsError (#62):
- Prevents silent data loss from cancelled downloads
- Raised when exiting context manager with unhandled pending work
- New
queue.pending_countproperty for checking pending downloads
🐛 Bug Fixes
Partial File Cleanup (#60):
- Cancelled downloads now properly clean up partial files
- Previously,
CancelledErrorbypassed cleanup logic, leaving corrupted files
🔧 Internal Improvements
Domain Purity (#61):
- Removed I/O from
FileConfigdomain model - Directory creation moved to
DownloadWorkerusing asyncaiofiles.os.makedirs - Examples updated to use
destination_subdirfor better organization
📝 Full Details
See the complete changelog: CHANGELOG.md
📦 Installation
pip install --upgrade rheopy🙏 Thank You
Thanks for using Rheo! If you encounter any issues with the migration, please open an issue.
v0.3.0: Simpified Shutdown & Download IDs
Release Notes
🚨 Breaking Changes
This release simplifies the shutdown API and adopts a download ID model for tracking and deduplication.
API Changes:
DownloadManager.cancel_all()removed- Use
close(wait_for_current=True/False)instead
- Use
WorkerPool.stop()andWorkerPool.request_shutdown()removed from the public interface- Worker pools now expose a single
shutdown(wait_for_current: bool)method
- Worker pools now expose a single
Migration Example (manager shutdown):
Before (v0.2.0)
async with DownloadManager(...) as manager:
...
await manager.cancel_all(wait_for_current=True)After (v0.3.0)
async with DownloadManager(...) as manager:
...
await manager.close(wait_for_current=True)Behavioural Note:
- The context manager (
async with DownloadManager(...)) continues to perform an immediate shutdown on exit (cancels in-flight downloads and cleans up resources). - Use
close(wait_for_current=True)if you need a graceful shutdown from user code.
✨ What's New
1. Simplified Shutdown API
WorkerPool:
- Single public method:
shutdown(wait_for_current: bool = True)wait_for_current=True→ graceful shutdown (finish in-flight downloads)wait_for_current=False→ immediate cancellation (cancel worker tasks, then clean up)
- Internal cancellation logic is inlined for clearer control flow.
_request_shutdown()is now private and used internally byshutdown().
DownloadManager:
close(wait_for_current: bool = False)is now the one place to control shutdown semantics:wait_for_current=True→ graceful shutdownwait_for_current=False→ immediate shutdown
- Context manager methods now delegate to lifecycle methods:
__aenter__→open()__aexit__→close(wait_for_current=False)
- This makes the public API smaller and easier to reason about.
2. Complete Download ID System
The Download ID system introduced in earlier work is now fully wired through the stack.
Key properties:
- Each download has a stable 16-character hex ID derived from
URL + relative_destination_path. - Same URL to different destinations ⇒ different IDs.
- Same URL to same destination ⇒ same ID (treated as a duplicate).
Where it’s used:
FileConfig.id– generated/computed ID for each download.DownloadInfo.id– stored with each tracked download.DownloadTracker– now keys by download_id instead of URL.- Worker events and tracker events – all carry
download_id. PriorityDownloadQueue– uses IDs for deduplication:- Duplicate downloads (same URL+destination) are skipped rather than re-queued.
User-visible effects:
- Adding the same
FileConfig(same URL+destination) multiple times will not enqueue duplicate downloads. - Tracker queries and integration points now consistently use
download_id(e.g.FileConfig.id) rather than raw URLs.
🐛 Fixes & Example Updates
-
Hash validation example (
03_hash_validation.py)- Fixed tracker lookup to use
file_config.idinstead of URL
- Fixed tracker lookup to use
-
Progress tracking example (
04_progress_tracking.py)- Fixed
on_progresshandler
- Fixed
-
Documentation updated to:
- Reflect shutdown API simplification (
close()instead ofshutdown()/cancel_all()). - Describe the Download ID system and deduplication behaviour.
- Reflect shutdown API simplification (
📝 Full Details
See the complete changelog:
CHANGELOG.md – v0.3.0
📦 Installation
pip install --upgrade rheopy
🙏 Thank You
Thanks for using Rheo! If you run into any issues upgrading to v0.3.0 or have feedback on the new shutdown and ID semantics, please open an issue.
v0.2.0: Download-Centric API & WorkerPool
Release Notes
Breaking Changes
This release includes a major refactor of the DownloadManager API to provide a cleaner, more intuitive interface focused on downloads rather than internal implementation details.
API Changes:
add_to_queue()→add()queue.join()→wait_until_complete()start_workers()→open()or use context managerstop_workers()→close()or use context managershutdown()→cancel_all()request_shutdown()removed (usecancel_all())max_workersparameter →max_concurrentworker=parameter →worker_factory=
Migration Example:
# Before (v0.1.0)
async with DownloadManager(max_workers=5) as manager:
await manager.add_to_queue(files)
await manager.queue.join()
# After (v0.2.0)
async with DownloadManager(max_concurrent=5) as manager:
await manager.add(files)
await manager.wait_until_complete()What's New
New DownloadManager Methods:
add(files)- Add files to download queuewait_until_complete(timeout=None)- Wait for all downloads to finishcancel_all(wait_for_current=False)- Cancel pending downloadsopen()/close()- Manual lifecycle managementis_activeproperty - Check if manager is running
WorkerPool Extraction (#43):
- New
WorkerPoolclass for managing worker lifecycle - Better separation of concerns: manager orchestrates, pool manages workers
- Comprehensive test suite with 15+ tests
Worker Isolation (#42):
- Per-task worker instances with isolated event emitters
- Prevents race conditions
WorkerFactoryProtocol for dependency injection
Package Reorganization (#41):
- Restructured into focused subdirectories:
worker/,validation/,retry/ - Improved code organization and extensibility
Examples & Docs:
- Unique filenames in examples to prevent overwrites
- All documentation updated with new API
- Fixed PyPI documentation links
Full Details
See the complete changelog: CHANGELOG.md
Installation
pip install --upgrade rheopyThank You
Thanks for using Rheo! If you encounter any issues with the migration, please open an issue.
Initial Release
Initial Release
First public release of Rheo - concurrent HTTP download orchestration with async I/O.
Features
- Concurrent downloads with worker pool
- Priority queue
- Hash validation (MD5, SHA256, SHA512)
- Retry logic with exponential backoff
- Real-time speed & ETA tracking
- Event-driven architecture
- CLI tool (
rheo download) - Full type hints
Installation
pip install rheopy See the README for full documentation.