diff --git a/README.md b/README.md index 6dd30cf..2086356 100644 --- a/README.md +++ b/README.md @@ -11,36 +11,50 @@ This is useful if you want to use `tqdm` to track the progress of a long-running ```bash pip install tqdm_publisher ``` +## Getting Started +### Basic Usage +To monitor the progress of an existing `tqdm` progress bar, simply swap the `tqdm`and `TQDMPublisher` constructors. Then, declare a callback function to handle progress updates, and subscribe it to the `TQDMPublisher` updates using the `subscribe` method _before iteration begins_. -## Usage +#### Original Code ```python import random -import asyncio +import time -from tqdm_publisher import TQDMPublisher +from tqdm import tqdm + +N_TASKS = 100 + +# Create a list of tasks +durations = [ random.uniform(0, 1.0) for _ in range(N_TASKS) ] + +# Create a progress bar +progress_bar = tqdm(durations) -async def sleep_func(sleep_duration = 1): - await asyncio.sleep(delay=sleep_duration) +# Iterate over the progress bar +for duration in progress_bar: + time.sleep(duration) # Execute the task +``` -async def run_multiple_sleeps(sleep_durations): +#### Modified Code - tasks = [] +```python +import random +import time - for sleep_duration in sleep_durations: - task = asyncio.create_task(sleep_func(sleep_duration=sleep_duration)) - tasks.append(task) +from tqdm_publisher import TQDMPublisher - progress_bar = TQDMPublisher(asyncio.as_completed(tasks), total=len(tasks)) - callback_id = progress_bar.subscribe(lambda info: print('Progress Update', info)) +N_TASKS = 100 +durations = [ random.uniform(0, 1.0) for _ in range(N_TASKS) ] +progress_bar = TQDMPublisher(durations) - for f in progress_bar: - await f +# Declare a callback function to handle progress updates +on_update = lambda info: print('Progress Update', info) - progress_bar.unsubscribe(callback_id) +# Subscribe the callback to the TQDMPublisher +progress_bar.subscribe(on_update) -number_of_tasks = 10**5 -sleep_durations = [random.uniform(0, 5.0) for _ in range(number_of_tasks)] -asyncio.run(run_multiple_sleeps(sleep_durations=sleep_durations)) +for duration in progress_bar: + time.sleep(duration) ``` ## Demo diff --git a/demo/client.py b/demo/client.py deleted file mode 100644 index 73bf883..0000000 --- a/demo/client.py +++ /dev/null @@ -1,12 +0,0 @@ -import subprocess -from pathlib import Path - -client_path = Path(__file__).parent / "client.html" - - -def main(): - subprocess.run(["open", client_path]) - - -if __name__ == "__main__": - main() diff --git a/demo/demo_cli.py b/demo/demo_cli.py deleted file mode 100644 index 766d806..0000000 --- a/demo/demo_cli.py +++ /dev/null @@ -1,41 +0,0 @@ -import subprocess -import sys -from pathlib import Path - -demo_base_path = Path(__file__).parent - -client_path = demo_base_path / "client.html" -server_path = demo_base_path / "server.py" - - -def main(): - if len(sys.argv) <= 1: - print("No command provided. Please specify a command (e.g. 'demo').") - return - - command = sys.argv[1] - - flags_list = sys.argv[2:] - - client_flag = "--client" in flags_list - server_flag = "--server" in flags_list - both_flags = "--server" in flags_list and "--client" in flags_list - - flags = dict( - client=not server_flag or both_flags, - server=not client_flag or both_flags, - ) - - if command == "demo": - if flags["client"]: - subprocess.run(["open", client_path]) - - if flags["server"]: - subprocess.run(["python", server_path]) - - else: - print(f"{command} is an invalid command.") - - -if __name__ == "__main__": - main() diff --git a/demo/server.py b/demo/server.py deleted file mode 100644 index 02d68dd..0000000 --- a/demo/server.py +++ /dev/null @@ -1,152 +0,0 @@ -#!/usr/bin/env python - -import asyncio -import json -import random -import threading -from typing import List -from uuid import uuid4 - -import websockets - -from tqdm_publisher import TQDMPublisher - - -async def sleep_func(sleep_duration: float = 1) -> float: - await asyncio.sleep(delay=sleep_duration) - - -def create_tasks(): - n = 10**5 - sleep_durations = [random.uniform(0, 5.0) for _ in range(n)] - tasks = [] - - for sleep_duration in sleep_durations: - task = asyncio.create_task(sleep_func(sleep_duration=sleep_duration)) - tasks.append(task) - - return tasks - - -class ProgressHandler: - def __init__(self): - self.started = False - self.callbacks = [] - self.callback_ids = [] - - def subscribe(self, callback): - self.callbacks.append(callback) - - if hasattr(self, "progress_bar"): - self._subscribe(callback) - - def unsubscribe(self, callback_id): - self.progress_bar.unsubscribe(callback_id) - - def clear(self): - self.callbacks = [] - self._clear() - - def _clear(self): - for callback_id in self.callback_ids: - self.unsubscribe(callback_id) - - self.callback_ids = [] - - async def run(self): - for f in self.progress_bar: - await f - - def stop(self): - self.started = False - self.clear() - self.thread.join() - - def _subscribe(self, callback): - callback_id = self.progress_bar.subscribe(callback) - self.callback_ids.append(callback_id) - - async def run(self): - if hasattr(self, "progress_bar"): - print("Progress bar already running") - return - - self.tasks = create_tasks() - self.progress_bar = TQDMPublisher(asyncio.as_completed(self.tasks), total=len(self.tasks)) - - for callback in self.callbacks: - self._subscribe(callback) - - for f in self.progress_bar: - await f - - self._clear() - del self.progress_bar - - def thread_loop(self): - while self.started: - asyncio.run(self.run()) - - def start(self): - if self.started: - return - - self.started = True - - self.thread = threading.Thread(target=self.thread_loop) # Start infinite loop of progress bar thread - self.thread.start() - - -progress_handler = ProgressHandler() - - -class WebSocketHandler: - def __init__(self): - self.clients = {} - - # Initialize with any state you need - pass - - def handle_task_result(self, task): - try: - task.result() # This will re-raise any exception that occurred in the task - except websockets.exceptions.ConnectionClosedOK: - print("WebSocket closed while sending message") - except Exception as e: - print(f"Error in task: {e}") - - async def handler(self, websocket): - id = str(uuid4()) - self.clients[id] = websocket # Register client connection - - progress_handler.start() # Start if not started - - def on_progress(info): - task = asyncio.create_task(websocket.send(json.dumps(info))) - task.add_done_callback(self.handle_task_result) # Handle task result or exception - - progress_handler.subscribe(on_progress) - - try: - async for message in websocket: - print("Message from client received:", message) - - finally: - # This is called when the connection is closed - del self.clients[id] - if len(self.clients) == 0: - progress_handler.stop() - - -async def spawn_server(): - handler = WebSocketHandler().handler - async with websockets.serve(handler, "", 8000): - await asyncio.Future() # run forever - - -def main(): - asyncio.run(spawn_server()) - - -if __name__ == "__main__": - main() diff --git a/pyproject.toml b/pyproject.toml index 2518f55..942e7fa 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -31,6 +31,7 @@ classifiers = [ "Operating System :: MacOS", "Operating System :: Unix", ] + dependencies = [ "tqdm>=4.49.0" ] @@ -50,8 +51,8 @@ demo = [ "Homepage" = "https://github.com/catalystneuro/tqdm_publisher" "Bug Tracker" = "https://github.com/catalystneuro/tqdm_publisher/issues" -[project.gui-scripts] -tqdm_publisher = "demo.demo_cli:main" +[project.scripts] +tqdm_publisher = "tqdm_publisher._demo._demo_command_line_interface:_command_line_interface" [tool.black] line-length = 120 diff --git a/src/tqdm_publisher/_demo/__init__.py b/src/tqdm_publisher/_demo/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/demo/client.html b/src/tqdm_publisher/_demo/_client.html similarity index 57% rename from demo/client.html rename to src/tqdm_publisher/_demo/_client.html index 67ed769..0e514eb 100644 --- a/demo/client.html +++ b/src/tqdm_publisher/_demo/_client.html @@ -12,6 +12,7 @@