- Create multiple progress bars to test concurrent subscriptions
-
-
-
-
-
-
-
-
-
-
diff --git a/src/tqdm_publisher/_demo/_client.js b/src/tqdm_publisher/_demo/_client.js
deleted file mode 100644
index edf3670..0000000
--- a/src/tqdm_publisher/_demo/_client.js
+++ /dev/null
@@ -1,72 +0,0 @@
-
-// Grab bar container from HTML
-const barContainer = document.querySelector('#bars');
-
-// Create a progress bar and append it to the bar container
-const createProgressBar = () => {
- const element = document.createElement('div');
- element.classList.add('progress');
- const progress = document.createElement('div');
- element.appendChild(progress);
- barContainer.appendChild(element);
- return { element, progress };
-}
-
-// Create a simple WebSocket client wrapper class
-class ProgressClient {
-
- #connect = (props = {}) => {
-
- const {
- onopen = () => {},
- onclose = () => {},
- onmessage = () => {}
- } = props;
-
- this.socket = new WebSocket('ws://localhost:8000');
- this.socket.addEventListener('open', onopen);
-
- // Attempt to reconnect every second if the connection is closed
- this.socket.addEventListener('close', () => {
- onclose();
- setTimeout(() => this.#connect(props), 1000);
- });
-
- this.socket.addEventListener('message', onmessage);
- }
-
- constructor(props) {
- this.#connect(props);
- }
-
- close() {
- this.socket.close();
- }
-
-}
-
-
-const bars = {} // Track progress bars
-
-
-// Update the specified progress bar when a message is received from the server
-const onProgressUpdate = (event) => {
- const { progress_bar_id, format_dict } = JSON.parse(event.data);
- bars[progress_bar_id].style.width = 100 * (format_dict.n / format_dict.total) + '%';
-}
-
-// Create a new WebSocket client
-const client = new ProgressClient({ onmessage: onProgressUpdate });
-
-// Declare that the HTML Button should create a new progress bar when clicked
-const button = document.querySelector('button');
-button.addEventListener('click', () => {
- const { element, progress } = createProgressBar(); // Create a progress bar
-
- barContainer.appendChild(element); // Render the progress bar
-
- const progress_bar_id = Math.random().toString(36).substring(7); // Create a unique ID for the progress bar
- bars[progress_bar_id] = progress; // Track the progress bar
-
- client.socket.send(JSON.stringify({ command: 'start', progress_bar_id })); // Send a message to the server to start the progress bar
-})
diff --git a/src/tqdm_publisher/_demo/_demo_command_line_interface.py b/src/tqdm_publisher/_demo/_demo_command_line_interface.py
index 2f1764c..2a6c0b7 100644
--- a/src/tqdm_publisher/_demo/_demo_command_line_interface.py
+++ b/src/tqdm_publisher/_demo/_demo_command_line_interface.py
@@ -3,12 +3,18 @@
import sys
from pathlib import Path
-from ._server import run_demo
+import webbrowser
+
+DEMOS = {
+ "single": "_single",
+ "multiple": "_multiple",
+ # "parallel": "_parallel",
+}
DEMO_BASE_FOLDER_PATH = Path(__file__).parent
-CLIENT_FILE_PATH = DEMO_BASE_FOLDER_PATH / "_client.html"
-SERVER_FILE_PATH = DEMO_BASE_FOLDER_PATH / "_server.py"
+CLIENT_PORT = 1234
+RELATIVE_DEMO_BASE_FOLDER_PATH = DEMO_BASE_FOLDER_PATH.relative_to(Path.cwd())
def _command_line_interface():
@@ -30,14 +36,23 @@ def _command_line_interface():
print(f"No flags are accepted at this time, but flags {flags_list} were received.")
return
- if command == "demo":
- # For convenience - automatically pop-up a browser window on the locally hosted HTML page
- if sys.platform == "win32":
- os.system(f'start "" "{CLIENT_FILE_PATH}"')
- else:
- subprocess.run(["open", CLIENT_FILE_PATH])
+ if command in DEMOS:
+
+ subpath = DEMOS[command]
+
+ # if command == "parallel":
+ # client_relative_path = Path(subpath) / "_client.py"
+ # subprocess.Popen(['python', str(DEMO_BASE_FOLDER_PATH / subpath / "_server.py")])
+ # subprocess.Popen(['python', str(DEMO_BASE_FOLDER_PATH / subpath / "_client.py")])
+
+ # else:
+
+ client_relative_path = Path(subpath) / "_client.html"
+ subprocess.Popen(['python', '-m', 'http.server', str(CLIENT_PORT), "-d", DEMO_BASE_FOLDER_PATH])
+
+ webbrowser.open_new_tab(f"http://localhost:{CLIENT_PORT}/{client_relative_path}")
- run_demo()
+ subprocess.run(['python', str(DEMO_BASE_FOLDER_PATH / subpath / "_server.py")])
else:
print(f"{command} is an invalid command.")
diff --git a/src/tqdm_publisher/_demo/_multiple/_client.html b/src/tqdm_publisher/_demo/_multiple/_client.html
new file mode 100644
index 0000000..a59ec1b
--- /dev/null
+++ b/src/tqdm_publisher/_demo/_multiple/_client.html
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
+
+
+
+
+
+ Multiple Bar Demo
+
+
+
+
+
+
+
+
+
+
+
+
tqdm_progress
+ Create multiple progress bars to test concurrent subscriptions
+
+
+
+
+
+
+
+
+
+
diff --git a/src/tqdm_publisher/_demo/_multiple/_client.js b/src/tqdm_publisher/_demo/_multiple/_client.js
new file mode 100644
index 0000000..29b5da9
--- /dev/null
+++ b/src/tqdm_publisher/_demo/_multiple/_client.js
@@ -0,0 +1,21 @@
+import { WebSocketManager } from '../utils/WebSocketManager.js';
+import { createProgressBar } from '../utils/elements.js';
+
+const bars = {} // Track progress bars
+
+// Update the specified progress bar when a message is received from the server
+const onProgressUpdate = (event) => {
+ const { request_id, format_dict } = JSON.parse(event.data);
+ bars[request_id].style.width = 100 * (format_dict.n / format_dict.total) + '%';
+}
+
+// Create a new WebSocket client
+const client = new WebSocketManager({ onmessage: onProgressUpdate });
+
+// Declare that the HTML Button should create a new progress bar when clicked
+const button = document.querySelector('button');
+button.addEventListener('click', () => {
+ const request_id = Math.random().toString(36).substring(7); // Create a unique ID for the progress bar
+ bars[request_id] = createProgressBar(); // Create and render a progress bar
+ client.socket.send(JSON.stringify({ command: 'start', request_id })); // Send a message to the server to start the progress bar
+})
diff --git a/src/tqdm_publisher/_demo/_multiple/_server.py b/src/tqdm_publisher/_demo/_multiple/_server.py
new file mode 100644
index 0000000..856b679
--- /dev/null
+++ b/src/tqdm_publisher/_demo/_multiple/_server.py
@@ -0,0 +1,69 @@
+import asyncio
+import json
+import threading
+import time
+
+import websockets
+
+import tqdm_publisher
+
+
+async def handler(websocket: websockets.WebSocketServerProtocol) -> None:
+ """Handle messages from the client and manage the client connections."""
+
+ class WebSocketProgressBar(threading.Thread):
+
+ def __init__(self, request_id: str):
+ super().__init__()
+ self.request_id = request_id
+
+ def update(self, format_dict) -> None:
+ """
+ This is the function that will run on every update of the TQDM object.
+
+ It will forward the progress to the client.
+ """
+ asyncio.run(
+ websocket.send(message=json.dumps(obj=dict(request_id=self.request_id, format_dict=format_dict)))
+ )
+
+ def run(self):
+ """
+ Emulate running the specified number of tasks by sleeping the specified amount of time on each iteration.
+
+ Defaults are chosen for a deterministic and regular update period of one second for a total time of one minute.
+ """
+ all_task_durations_in_seconds = [.1 for _ in range(100)] # Ten seconds of one hundred tasks
+ progress_bar = self.progress_bar = tqdm_publisher.TQDMPublisher(iterable=all_task_durations_in_seconds)
+ progress_bar.subscribe(callback=self.update)
+
+ for task_duration in progress_bar:
+ time.sleep(task_duration)
+
+ # def start(self):
+ # thread = threading.Thread(target=self.run_progress_bar)
+ # thread.start()
+
+
+ # Wait for messages from the client
+ async for message in websocket:
+ message_from_client = json.loads(message)
+
+ if message_from_client["command"] == "start":
+ progress_bar = WebSocketProgressBar(request_id=message_from_client["request_id"])
+ progress_bar.start()
+
+
+async def spawn_server() -> None:
+ """Spawn the server asynchronously."""
+ async with websockets.serve(ws_handler=handler, host="", port=8000):
+ await asyncio.Future()
+
+
+def run_demo() -> None:
+ """Trigger the execution of the asynchronous spawn."""
+ asyncio.run(spawn_server())
+
+
+if __name__ == "__main__":
+ run_demo()
\ No newline at end of file
diff --git a/src/tqdm_publisher/_demo/_single_bar/_client.html b/src/tqdm_publisher/_demo/_single_bar/_client.html
new file mode 100644
index 0000000..9ce3b3d
--- /dev/null
+++ b/src/tqdm_publisher/_demo/_single_bar/_client.html
@@ -0,0 +1,25 @@
+
+
+
+
+
+ Single Bar Demo
+
+
+
+
+
+
+
+
tqdm_progress
+ Single Bar Demo
+
+
+
+
+
+
+
+
+
+
diff --git a/src/tqdm_publisher/_demo/_single_bar/_client.js b/src/tqdm_publisher/_demo/_single_bar/_client.js
new file mode 100644
index 0000000..1596925
--- /dev/null
+++ b/src/tqdm_publisher/_demo/_single_bar/_client.js
@@ -0,0 +1,25 @@
+import { WebSocketManager } from '../utils/WebSocketManager.js';
+import { createProgressBar } from '../utils/elements.js';
+
+
+const bar = createProgressBar(); // Create and render a progress bar
+
+// Update the specified progress bar when a message is received from the server
+const onProgressUpdate = (event) => {
+ const { format_dict } = JSON.parse(event.data);
+ const ratio = format_dict.n / format_dict.total;
+ bar.style.width = `${100 * ratio}%`;
+
+ if (ratio === 1) button.removeAttribute('disabled'); // Enable the button when the progress bar is complete
+}
+
+// Create a new WebSocket client
+const client = new WebSocketManager({ onmessage: onProgressUpdate });
+
+// Declare that the HTML Button should create a new progress bar when clicked
+const button = document.querySelector('button');
+button.addEventListener('click', () => {
+ button.setAttribute('disabled', true); // Disable the button to prevent multiple progress bars from being created
+ bar.style.width = 0; // Reset the progress bar
+ client.socket.send(JSON.stringify({ command: 'start' })); // Send a message to the server to start the progress bar
+})
diff --git a/src/tqdm_publisher/_demo/_server.py b/src/tqdm_publisher/_demo/_single_bar/_server.py
similarity index 62%
rename from src/tqdm_publisher/_demo/_server.py
rename to src/tqdm_publisher/_demo/_single_bar/_server.py
index ac821dc..ff3da61 100644
--- a/src/tqdm_publisher/_demo/_server.py
+++ b/src/tqdm_publisher/_demo/_single_bar/_server.py
@@ -8,13 +8,13 @@
import tqdm_publisher
-def start_progress_bar(*, progress_bar_id: str, client_callback: callable) -> None:
+def start_progress_bar(*, progress_callback: callable) -> None:
"""
Emulate running the specified number of tasks by sleeping the specified amount of time on each iteration.
Defaults are chosen for a deterministic and regular update period of one second for a total time of one minute.
"""
- all_task_durations_in_seconds = [1.0 for _ in range(60)] # One minute at one second per update
+ all_task_durations_in_seconds = [.1 for _ in range(100)] # Ten seconds of one hundred tasks
progress_bar = tqdm_publisher.TQDMPublisher(iterable=all_task_durations_in_seconds)
def run_function_on_progress_update(format_dict: dict) -> None:
@@ -25,9 +25,11 @@ def run_function_on_progress_update(format_dict: dict) -> None:
on outside parameters must be achieved by defining those fields at an outer scope and defining this
server-specific callback inside the local scope.
- In this demo, we will execute the `client_callback` whose protocol is known only to the WebSocketHandler.
+ In this demo, we will execute the `progress_callback` whose protocol is known only to the WebSocketHandler.
+
+ This specifically requires the `id` of the progress bar and the `format_dict` of the TQDM instance.
"""
- client_callback(progress_bar_id=progress_bar_id, format_dict=format_dict)
+ progress_callback(id=progress_bar.id, format_dict=format_dict)
progress_bar.subscribe(callback=run_function_on_progress_update)
@@ -35,29 +37,33 @@ def run_function_on_progress_update(format_dict: dict) -> None:
time.sleep(task_duration)
-async def handler(websocket: websockets.WebSocketServerProtocol) -> None:
- """Handle messages from the client and manage the client connections."""
+def send_message_to_client(*, websocket: websockets.WebSocketServerProtocol, message: dict) -> None:
+ """
+ Send a message to a specific client.
- def forward_progress_to_client(*, progress_bar_id: str, format_dict: dict) -> None:
- """
- This is the function that will run on every update of the TQDM object.
+ This expects a WebSocket connection and a message (dict) to send.
+ """
- It will forward the progress to the client.
- """
- asyncio.run(
- websocket.send(message=json.dumps(obj=dict(progress_bar_id=progress_bar_id, format_dict=format_dict)))
- )
+ asyncio.run(
+ websocket.send(message=json.dumps(obj=message))
+ )
+
+async def handler(websocket: websockets.WebSocketServerProtocol) -> None:
+ """Handle messages from the client and manage the client connections."""
# Wait for messages from the client
async for message in websocket:
message_from_client = json.loads(message)
if message_from_client["command"] == "start":
+
+ # Start the progress bar in a separate thread
thread = threading.Thread(
target=start_progress_bar,
+
+ # On each update of the progress bar, send this update to the requesting client
kwargs=dict(
- progress_bar_id=message_from_client["progress_bar_id"],
- client_callback=forward_progress_to_client,
+ progress_callback=lambda id, format_dict: send_message_to_client(websocket, dict(id=id, format_dict=format_dict))
),
)
thread.start()
@@ -72,3 +78,7 @@ async def spawn_server() -> None:
def run_demo() -> None:
"""Trigger the execution of the asynchronous spawn."""
asyncio.run(spawn_server())
+
+
+if __name__ == "__main__":
+ run_demo()
\ No newline at end of file
diff --git a/src/tqdm_publisher/_demo/utils/WebSocketManager.js b/src/tqdm_publisher/_demo/utils/WebSocketManager.js
new file mode 100644
index 0000000..50c845a
--- /dev/null
+++ b/src/tqdm_publisher/_demo/utils/WebSocketManager.js
@@ -0,0 +1,32 @@
+// Create a simple WebSocket client wrapper class
+export class WebSocketManager {
+
+ #connect = (props = {}) => {
+
+ const {
+ onopen = () => {},
+ onclose = () => {},
+ onmessage = () => {}
+ } = props;
+
+ this.socket = new WebSocket('ws://localhost:8000');
+ this.socket.addEventListener('open', onopen);
+
+ // Attempt to reconnect every second if the connection is closed
+ this.socket.addEventListener('close', () => {
+ onclose();
+ setTimeout(() => this.#connect(props), 1000);
+ });
+
+ this.socket.addEventListener('message', onmessage);
+ }
+
+ constructor(props) {
+ this.#connect(props);
+ }
+
+ close() {
+ this.socket.close();
+ }
+
+}
diff --git a/src/tqdm_publisher/_demo/utils/elements.js b/src/tqdm_publisher/_demo/utils/elements.js
new file mode 100644
index 0000000..f316bb1
--- /dev/null
+++ b/src/tqdm_publisher/_demo/utils/elements.js
@@ -0,0 +1,12 @@
+
+const barContainer = document.querySelector('#bars');
+
+// Create a progress bar and append it to the bar container
+export const createProgressBar = () => {
+ const element = document.createElement('div');
+ element.classList.add('progress');
+ const progress = document.createElement('div');
+ element.appendChild(progress);
+ barContainer.appendChild(element); // Render the progress bar
+ return progress;
+}
diff --git a/src/tqdm_publisher/_publisher.py b/src/tqdm_publisher/_publisher.py
index acd723d..d5cc3b3 100644
--- a/src/tqdm_publisher/_publisher.py
+++ b/src/tqdm_publisher/_publisher.py
@@ -7,6 +7,7 @@
class TQDMPublisher(base_tqdm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
+ self.id = str(uuid4())
self.callbacks = {}
# Override the update method to run callbacks