From b917251be77aefe805e5a1a73f432c93465fb223 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Math=C3=AFs=20F=C3=A9d=C3=A9rico?= Date: Wed, 16 Feb 2022 22:29:55 +0100 Subject: [PATCH 01/19] :tada: :art: Add persitent colors --- pyflow/blocks/block.py | 24 +++++++++++++++++++----- pyflow/blocks/codeblock.py | 13 ++++++++++--- pyflow/blocks/executableblock.py | 6 +++--- pyflow/core/kernel.py | 1 + 4 files changed, 33 insertions(+), 11 deletions(-) diff --git a/pyflow/blocks/block.py b/pyflow/blocks/block.py index 25b326dc..ce4e4f97 100644 --- a/pyflow/blocks/block.py +++ b/pyflow/blocks/block.py @@ -72,8 +72,8 @@ def __init__( self.sockets_in: List[Socket] = [] self.sockets_out: List[Socket] = [] - self._pen_outline = QPen(QColor("#7F000000")) - self._pen_outline_selected = QPen(QColor("#FFFFA637")) + self._pen_outline = QPen(QColor("#00000000")) + self._pen_outline_selected = QPen(QColor("#800030FF")) self._brush_background = QBrush(BACKGROUND_COLOR) self.setFlag(QGraphicsItem.GraphicsItemFlag.ItemIsSelectable) @@ -138,12 +138,26 @@ def paint( path_outline.addRoundedRect( 0, 0, self.width, self.height, self.edge_size, self.edge_size ) - painter.setPen( - self._pen_outline_selected if self.isSelected() else self.pen_outline - ) + painter.setPen(self.pen_outline) painter.setBrush(Qt.BrushStyle.NoBrush) painter.drawPath(path_outline.simplified()) + # selection inner outline + if self.isSelected(): + path_in_outline = QPainterPath() + outline_width = self.pen_outline.widthF() + path_in_outline.addRoundedRect( + 2 * outline_width, + 2 * outline_width, + self.width - 4 * outline_width, + self.height - 4 * outline_width, + self.edge_size - 2 * outline_width, + self.edge_size - 2 * outline_width, + ) + painter.setPen(self._pen_outline_selected) + painter.setBrush(Qt.BrushStyle.NoBrush) + painter.drawPath(path_in_outline.simplified()) + def add_socket(self, socket: Socket): """Add a socket to the block.""" if socket.socket_type == "input": diff --git a/pyflow/blocks/codeblock.py b/pyflow/blocks/codeblock.py index 14894689..362b3a7e 100644 --- a/pyflow/blocks/codeblock.py +++ b/pyflow/blocks/codeblock.py @@ -63,13 +63,16 @@ def __init__(self, source: str = "", **kwargs): self._splitter_size = [1, 1] self._cached_stdout = "" self.has_been_run = False + self.is_crashed = False self.blocks_to_run = [] self._pen_outlines = [ - QPen(QColor("#7F000000")), # Idle - QPen(QColor("#FF0000")), # Running - QPen(QColor("#00ff00")), # Transmitting + QPen(QColor("#00000000")), # No outline: Idle + QPen(QColor("#fffc6107")), # Orange: Running + QPen(QColor("#80fc6107")), # Dark orange: Transmitting ] + self._pen_has_been_run = QPen(QColor("#158000")) # Dark green: has been run + self._pen_crashed = QPen(QColor("#ff0000")) # Red: Crashed self.output_panel_background_color = "#1E1E1E" @@ -268,6 +271,10 @@ def source(self, value: str): @property def pen_outline(self) -> QPen: """The current pen used to draw the outline of the CodeBlock.""" + if self.is_crashed: + return self._pen_crashed + if self.has_been_run: + return self._pen_has_been_run return self._pen_outlines[self.run_state] @property diff --git a/pyflow/blocks/executableblock.py b/pyflow/blocks/executableblock.py index 46ef692e..84b768b4 100644 --- a/pyflow/blocks/executableblock.py +++ b/pyflow/blocks/executableblock.py @@ -123,9 +123,8 @@ def transmitting_animation_out(self): """ for elem in self.transmitting_queue[0]: # Reset color only if the block will not be run - if hasattr(elem, "has_been_run"): - if elem.has_been_run is True: - elem.run_state = 0 + if hasattr(elem, "has_been_run") and not elem.has_been_run: + pass else: elem.run_state = 0 @@ -332,6 +331,7 @@ def reset_has_been_run(self): def error_occured(self): """Interrupt the kernel if an error occured""" + self.is_crashed = True self._interrupt_execution() @property diff --git a/pyflow/core/kernel.py b/pyflow/core/kernel.py index bb555b3a..d81cc1b1 100644 --- a/pyflow/core/kernel.py +++ b/pyflow/core/kernel.py @@ -78,6 +78,7 @@ def run_block(self, block: "ExecutableBlock", code: str): worker = Worker(self, block, code) # Change color to running block.run_state = 1 + block.is_crashed = False worker.signals.stdout.connect(block.handle_stdout) worker.signals.image.connect(block.handle_image) worker.signals.finished.connect(self.run_queue) From 8fc2034edbba419dc561e1a254fef3ffad73a5d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Math=C3=AFs=20F=C3=A9d=C3=A9rico?= Date: Wed, 16 Feb 2022 23:06:41 +0100 Subject: [PATCH 02/19] :hammer: Refactor ExecutableBlock.run_state using Enum --- pyflow/blocks/codeblock.py | 31 +++++------- pyflow/blocks/executableblock.py | 58 ++++++++-------------- pyflow/core/edge.py | 4 +- pyflow/core/kernel.py | 4 +- pyflow/core/worker.py | 1 - tests/integration/blocks/test_codeblock.py | 5 +- tests/integration/blocks/test_flow.py | 9 ++-- 7 files changed, 47 insertions(+), 65 deletions(-) diff --git a/pyflow/blocks/codeblock.py b/pyflow/blocks/codeblock.py index 362b3a7e..a1288cd3 100644 --- a/pyflow/blocks/codeblock.py +++ b/pyflow/blocks/codeblock.py @@ -11,7 +11,7 @@ from pyflow.blocks.block import Block from pyflow.core.edge import Edge -from pyflow.blocks.executableblock import ExecutableBlock +from pyflow.blocks.executableblock import ExecutableBlock, ExecutableState from pyflow.blocks.pyeditor import PythonEditor from pyflow.core.add_button import AddEdgeButton, AddNewBlockButton @@ -62,17 +62,15 @@ def __init__(self, source: str = "", **kwargs): self.output_closed = True self._splitter_size = [1, 1] self._cached_stdout = "" - self.has_been_run = False - self.is_crashed = False self.blocks_to_run = [] - self._pen_outlines = [ - QPen(QColor("#00000000")), # No outline: Idle - QPen(QColor("#fffc6107")), # Orange: Running - QPen(QColor("#80fc6107")), # Dark orange: Transmitting - ] - self._pen_has_been_run = QPen(QColor("#158000")) # Dark green: has been run - self._pen_crashed = QPen(QColor("#ff0000")) # Red: Crashed + self._pen_outlines = { + ExecutableState.IDLE: QPen(QColor("#00000000")), # No outline + ExecutableState.RUNNING: QPen(QColor("#fffc6107")), # Orange + ExecutableState.PENDING: QPen(QColor("#80fc6107")), # Dark orange + ExecutableState.DONE: QPen(QColor("#158000")), # Dark green + ExecutableState.CRASHED: QPen(QColor("#ff0000")), # Red: Crashed + } self.output_panel_background_color = "#1E1E1E" @@ -136,14 +134,14 @@ def init_add_newblock_button(self): def handle_run_right(self): """Called when the button for "Run All" was pressed.""" - if self.run_state != 0: + if self.run_state in (ExecutableState.PENDING, ExecutableState.RUNNING): self._interrupt_execution() else: self.run_right() def handle_run_left(self): """Called when the button for "Run Left" was pressed.""" - if self.run_state != 0: + if self.run_state in (ExecutableState.PENDING, ExecutableState.RUNNING): self._interrupt_execution() else: self.run_left() @@ -262,19 +260,14 @@ def source(self, value: str): if value != self._source: # If text has changed, set self and all output blocks to not run output_blocks, _ = self.custom_bfs(self, reverse=True) - for block in output_blocks: - block.has_been_run = False - self.has_been_run = False + for block in output_blocks + [self]: + block.run_state = ExecutableState.IDLE self.source_editor.setText(value) self._source = value @property def pen_outline(self) -> QPen: """The current pen used to draw the outline of the CodeBlock.""" - if self.is_crashed: - return self._pen_crashed - if self.has_been_run: - return self._pen_has_been_run return self._pen_outlines[self.run_state] @property diff --git a/pyflow/blocks/executableblock.py b/pyflow/blocks/executableblock.py index 84b768b4..95fb8da2 100644 --- a/pyflow/blocks/executableblock.py +++ b/pyflow/blocks/executableblock.py @@ -8,6 +8,7 @@ """ from typing import List, OrderedDict, Set, Union +from enum import Enum, auto from abc import abstractmethod from PyQt5.QtCore import QTimer from PyQt5.QtWidgets import QApplication @@ -17,6 +18,14 @@ from pyflow.core.edge import Edge +class ExecutableState(Enum): + IDLE = 0 + RUNNING = 1 + PENDING = 2 + DONE = 3 + CRASHED = 4 + + class ExecutableBlock(Block): """ @@ -37,8 +46,7 @@ def __init__(self, **kwargs): """ super().__init__(**kwargs) - self.has_been_run = False - self._run_state = 0 + self._run_state = ExecutableState.IDLE # Each element is a list of blocks/edges to be animated # Running will paint each element one after the other @@ -85,18 +93,18 @@ def run_code(self): if kernel.busy is False: kernel.run_queue() - self.has_been_run = True + self.run_state = ExecutableState.PENDING def execution_finished(self): """Reset the text of the run buttons.""" - self.run_state = 0 + if self.run_state != ExecutableState.CRASHED: + self.run_state = ExecutableState.DONE self.blocks_to_run = [] def _interrupt_execution(self): """Interrupt an execution, reset the blocks in the queue.""" for block, _ in self.scene().kernel.execution_queue: # Reset the blocks that have not been run - block.reset_has_been_run() block.execution_finished() # Clear kernel execution queue self.scene().kernel.execution_queue = [] @@ -110,9 +118,6 @@ def transmitting_animation_in(self): Animate the visual flow Set color to transmitting and set a timer before switching to normal """ - for elem in self.transmitting_queue[0]: - # Set color to transmitting - elem.run_state = 2 QApplication.processEvents() QTimer.singleShot(self.transmitting_delay, self.transmitting_animation_out) @@ -121,13 +126,6 @@ def transmitting_animation_out(self): Animate the visual flow After the timer, set color to normal and move on with the queue """ - for elem in self.transmitting_queue[0]: - # Reset color only if the block will not be run - if hasattr(elem, "has_been_run") and not elem.has_been_run: - pass - else: - elem.run_state = 0 - QApplication.processEvents() self.transmitting_queue.pop(0) if self.transmitting_queue: @@ -269,17 +267,15 @@ def right_traversal(self): def run_blocks(self): """Run a list of blocks.""" - for block in self.blocks_to_run[::-1]: - if not block.has_been_run: + for block in self.blocks_to_run[::-1] + [self]: + if block.run_state == ExecutableState.IDLE: block.run_code() - if not self.has_been_run: - self.run_code() def run_left(self): """Run all of the block's dependencies and then run the block.""" - # Reset has_been_run to make sure that the self is run again - self.has_been_run = False + # Reset state to make sure that the self is run again + self.run_state = ExecutableState.IDLE # To avoid crashing when spamming the button if self.transmitting_queue: @@ -325,13 +321,9 @@ def run_right(self): # Start transmitting animation self.transmitting_animation_in() - def reset_has_been_run(self): - """Called when the output is an error.""" - self.has_been_run = False - def error_occured(self): """Interrupt the kernel if an error occured""" - self.is_crashed = True + self.run_state = ExecutableState.CRASHED self._interrupt_execution() @property @@ -346,19 +338,13 @@ def source(self, value: str): raise NotImplementedError("source(self) should be overriden") @property - def run_state(self) -> int: - """Run state. - - Describe the current state of the ExecutableBlock: - - 0: idle. - - 1: running. - - 2: transmitting. - - """ + def run_state(self) -> ExecutableState: + """The current state of the ExecutableBlock.""" return self._run_state @run_state.setter - def run_state(self, value: int): + def run_state(self, value: ExecutableState): + assert isinstance(value, ExecutableState) self._run_state = value # Update to force repaint self.update() diff --git a/pyflow/core/edge.py b/pyflow/core/edge.py index fe8e05fa..0408bdb3 100644 --- a/pyflow/core/edge.py +++ b/pyflow/core/edge.py @@ -74,7 +74,7 @@ def __init__( self.pens = [self._pen, self._pen_running, self._pen_transmitting] # 0 for normal, 1 for running, 2 for transmitting - self.run_state = 0 + self._run_state = 0 self.setFlag(QGraphicsPathItem.GraphicsItemFlag.ItemIsSelectable) self.setZValue(-1) @@ -272,6 +272,8 @@ def run_state(self) -> int: @run_state.setter def run_state(self, value: int): + if hasattr(value, "value"): + value = min(len(self.pens) - 1, value.value) self._run_state = value # Update to force repaint self.update() diff --git a/pyflow/core/kernel.py b/pyflow/core/kernel.py index d81cc1b1..ffa2add4 100644 --- a/pyflow/core/kernel.py +++ b/pyflow/core/kernel.py @@ -6,6 +6,7 @@ import queue from typing import TYPE_CHECKING, List, Tuple from jupyter_client.manager import start_new_kernel +from pyflow.blocks.executableblock import ExecutableState from pyflow.core.worker import Worker from pyflow.logging import log_init_time, get_logger @@ -77,8 +78,7 @@ def run_block(self, block: "ExecutableBlock", code: str): """ worker = Worker(self, block, code) # Change color to running - block.run_state = 1 - block.is_crashed = False + block.run_state = ExecutableState.RUNNING worker.signals.stdout.connect(block.handle_stdout) worker.signals.image.connect(block.handle_image) worker.signals.finished.connect(self.run_queue) diff --git a/pyflow/core/worker.py b/pyflow/core/worker.py index 61939871..f3c8e020 100644 --- a/pyflow/core/worker.py +++ b/pyflow/core/worker.py @@ -44,7 +44,6 @@ async def run_code(self): elif output_type == "image": self.signals.image.emit(output) elif output_type == "error": - self.block.reset_has_been_run() self.signals.error.emit() self.signals.stdout.emit(output) self.signals.finished.emit() diff --git a/tests/integration/blocks/test_codeblock.py b/tests/integration/blocks/test_codeblock.py index 03fe0788..33ec1126 100644 --- a/tests/integration/blocks/test_codeblock.py +++ b/tests/integration/blocks/test_codeblock.py @@ -11,6 +11,7 @@ import pytest from pyflow.blocks.codeblock import CodeBlock +from pyflow.blocks.executableblock import ExecutableState from tests.integration.utils import apply_function_inapp, CheckingQueue, InAppTest @@ -45,7 +46,7 @@ def testing_run(msgQueue: CheckingQueue): pyautogui.mouseUp(button="left") time.sleep((test_block.transmitting_duration / 1000) + 0.2) - while test_block.run_state != 0: + while test_block.run_state != ExecutableState.DONE: time.sleep(0.1) msgQueue.check_equal(test_block.stdout.strip(), expected_result) @@ -76,7 +77,7 @@ def run_block(): msgQueue.run_lambda(run_block) time.sleep(0.1) # wait for the lambda to complete. - while block_of_test.run_state != 0: + while block_of_test.run_state != ExecutableState.DONE: time.sleep(0.1) # wait for the execution to finish. time.sleep(0.1) diff --git a/tests/integration/blocks/test_flow.py b/tests/integration/blocks/test_flow.py index 28842af0..c1436b48 100644 --- a/tests/integration/blocks/test_flow.py +++ b/tests/integration/blocks/test_flow.py @@ -9,6 +9,7 @@ import time from pyflow.blocks.codeblock import CodeBlock +from pyflow.blocks.executableblock import ExecutableState from tests.integration.utils import apply_function_inapp, CheckingQueue, InAppTest @@ -47,7 +48,7 @@ def run_block(): msgQueue.run_lambda(run_block) time.sleep((block_to_run.transmitting_duration / 1000) + 0.2) - while block_to_run.run_state != 0: + while block_to_run.run_state != ExecutableState.DONE: time.sleep(0.1) # 6 and not 6\n6 @@ -72,7 +73,7 @@ def run_block(): msgQueue.run_lambda(run_block) time.sleep((block_to_run.transmitting_duration / 1000) + 0.2) - while block_to_run.run_state != 0: + while block_to_run.run_state != ExecutableState.DONE: time.sleep(0.1) msgQueue.check_equal(block_to_run.stdout.strip(), "6") @@ -97,7 +98,7 @@ def run_block(): msgQueue.run_lambda(run_block) time.sleep((block_to_run.transmitting_duration / 1000) + 0.2) - while block_to_run.run_state != 0: + while block_to_run.run_state != ExecutableState.DONE: time.sleep(0.1) msgQueue.check_equal(block_to_run.stdout.strip(), "1") @@ -119,7 +120,7 @@ def run_block(): msgQueue.run_lambda(run_block) time.sleep((block_to_run.transmitting_duration / 1000) + 0.2) - while block_to_run.run_state != 0: + while block_to_run.run_state != ExecutableState.DONE: time.sleep(0.1) # Just check that it doesn't crash From 793b9fb337d784258a041826c33ef9425290b498 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Math=C3=AFs=20F=C3=A9d=C3=A9rico?= Date: Wed, 16 Feb 2022 23:37:39 +0100 Subject: [PATCH 03/19] :hammer: Add Executable class --- pyflow/blocks/codeblock.py | 2 +- pyflow/blocks/executableblock.py | 31 ++++---------------- pyflow/core/edge.py | 50 +++++++++++--------------------- pyflow/core/executable.py | 27 +++++++++++++++++ pyflow/core/kernel.py | 3 +- 5 files changed, 51 insertions(+), 62 deletions(-) create mode 100644 pyflow/core/executable.py diff --git a/pyflow/blocks/codeblock.py b/pyflow/blocks/codeblock.py index a1288cd3..1de87ab0 100644 --- a/pyflow/blocks/codeblock.py +++ b/pyflow/blocks/codeblock.py @@ -66,7 +66,7 @@ def __init__(self, source: str = "", **kwargs): self._pen_outlines = { ExecutableState.IDLE: QPen(QColor("#00000000")), # No outline - ExecutableState.RUNNING: QPen(QColor("#fffc6107")), # Orange + ExecutableState.RUNNING: QPen(QColor("#ff6107ff")), # Purple ExecutableState.PENDING: QPen(QColor("#80fc6107")), # Dark orange ExecutableState.DONE: QPen(QColor("#158000")), # Dark green ExecutableState.CRASHED: QPen(QColor("#ff0000")), # Red: Crashed diff --git a/pyflow/blocks/executableblock.py b/pyflow/blocks/executableblock.py index 95fb8da2..462afdfd 100644 --- a/pyflow/blocks/executableblock.py +++ b/pyflow/blocks/executableblock.py @@ -8,7 +8,6 @@ """ from typing import List, OrderedDict, Set, Union -from enum import Enum, auto from abc import abstractmethod from PyQt5.QtCore import QTimer from PyQt5.QtWidgets import QApplication @@ -16,17 +15,10 @@ from pyflow.blocks.block import Block from pyflow.core.socket import Socket from pyflow.core.edge import Edge +from pyflow.core.executable import Executable, ExecutableState -class ExecutableState(Enum): - IDLE = 0 - RUNNING = 1 - PENDING = 2 - DONE = 3 - CRASHED = 4 - - -class ExecutableBlock(Block): +class ExecutableBlock(Block, Executable): """ Executable Block @@ -44,9 +36,8 @@ def __init__(self, **kwargs): Create a new executable block. Do not call this method except when inheriting from this class. """ - super().__init__(**kwargs) - - self._run_state = ExecutableState.IDLE + Block.__init__(self, **kwargs) + Executable.__init__(self) # Each element is a list of blocks/edges to be animated # Running will paint each element one after the other @@ -268,7 +259,7 @@ def right_traversal(self): def run_blocks(self): """Run a list of blocks.""" for block in self.blocks_to_run[::-1] + [self]: - if block.run_state == ExecutableState.IDLE: + if block.run_state != ExecutableState.DONE: block.run_code() def run_left(self): @@ -337,18 +328,6 @@ def source(self) -> str: def source(self, value: str): raise NotImplementedError("source(self) should be overriden") - @property - def run_state(self) -> ExecutableState: - """The current state of the ExecutableBlock.""" - return self._run_state - - @run_state.setter - def run_state(self, value: ExecutableState): - assert isinstance(value, ExecutableState) - self._run_state = value - # Update to force repaint - self.update() - def handle_stdout(self, value: str): """Handle the stdout signal.""" diff --git a/pyflow/core/edge.py b/pyflow/core/edge.py index 0408bdb3..db44ae4a 100644 --- a/pyflow/core/edge.py +++ b/pyflow/core/edge.py @@ -17,9 +17,10 @@ from pyflow.core.serializable import Serializable from pyflow.core.socket import Socket +from pyflow.core.executable import Executable, ExecutableState -class Edge(QGraphicsPathItem, Serializable): +class Edge(QGraphicsPathItem, Serializable, Executable): """Base class for directed edges in Pyflow.""" @@ -28,12 +29,12 @@ class Edge(QGraphicsPathItem, Serializable): def __init__( self, - edge_width: float = 4.0, + edge_width: float = 6.0, path_type=DEFAULT_DATA["path_type"], edge_color="#001000", - edge_selected_color="#00ff00", + edge_selected_color="#0030FF", edge_running_color="#FF0000", - edge_transmitting_color="#00ff00", + edge_pending_color="#00ff00", source: QPointF = QPointF(0, 0), destination: QPointF = QPointF(0, 0), source_socket: Socket = None, @@ -55,6 +56,8 @@ def __init__( Serializable.__init__(self) QGraphicsPathItem.__init__(self, parent=None) + Executable.__init__(self) + self._pen = QPen(QColor(edge_color)) self._pen.setWidthF(edge_width) @@ -68,13 +71,14 @@ def __init__( self._pen_running = QPen(QColor(edge_running_color)) self._pen_running.setWidthF(edge_width) - self._pen_transmitting = QPen(QColor(edge_transmitting_color)) - self._pen_transmitting.setWidthF(edge_width) - - self.pens = [self._pen, self._pen_running, self._pen_transmitting] + self._pen_pending = QPen(QColor(edge_pending_color)) + self._pen_pending.setWidthF(edge_width) - # 0 for normal, 1 for running, 2 for transmitting - self._run_state = 0 + self.state_pens = { + ExecutableState.IDLE: self._pen, + ExecutableState.RUNNING: self._pen_running, + ExecutableState.PENDING: self._pen_pending, + } self.setFlag(QGraphicsPathItem.GraphicsItemFlag.ItemIsSelectable) self.setZValue(-1) @@ -117,8 +121,8 @@ def paint( self, painter: QPainter, option: QStyleOptionGraphicsItem, # pylint:disable=unused-argument - widget: Optional[QWidget] = None, - ): # pylint:disable=unused-argument + widget: Optional[QWidget] = None, # pylint:disable=unused-argument + ): """Paint the edge.""" self.update_path() if self.isSelected(): @@ -126,7 +130,7 @@ def paint( elif self.destination_socket is None: pen = self._pen_dragging else: - pen = self.pens[self.run_state] + pen = self.state_pens[self.run_state] painter.setPen(pen) painter.setBrush(Qt.BrushStyle.NoBrush) painter.drawPath(self.path()) @@ -257,23 +261,3 @@ def deserialize(self, data: OrderedDict, hashmap: dict = None, restore_id=True): self.update_path() except KeyError: self.remove() - - @property - def run_state(self) -> int: - """Run state. - - Describe the current state of the Edge: - - 0: idle. - - 1: running. - - 2: transmitting. - - """ - return self._run_state - - @run_state.setter - def run_state(self, value: int): - if hasattr(value, "value"): - value = min(len(self.pens) - 1, value.value) - self._run_state = value - # Update to force repaint - self.update() diff --git a/pyflow/core/executable.py b/pyflow/core/executable.py new file mode 100644 index 00000000..91f8f874 --- /dev/null +++ b/pyflow/core/executable.py @@ -0,0 +1,27 @@ +from enum import Enum + + +class ExecutableState(Enum): + IDLE = 0 + RUNNING = 1 + PENDING = 2 + DONE = 3 + CRASHED = 4 + + +class Executable: + def __init__(self) -> None: + self._run_state = ExecutableState.IDLE + + @property + def run_state(self) -> ExecutableState: + """The current state of the Executable.""" + return self._run_state + + @run_state.setter + def run_state(self, value: ExecutableState): + assert isinstance(value, ExecutableState) + self._run_state = value + # Update to force repaint if available + if hasattr(self, "update"): + self.update() diff --git a/pyflow/core/kernel.py b/pyflow/core/kernel.py index ffa2add4..217236ad 100644 --- a/pyflow/core/kernel.py +++ b/pyflow/core/kernel.py @@ -76,9 +76,8 @@ def run_block(self, block: "ExecutableBlock", code: str): block: CodeBlock to send the output to code: String representing a piece of Python code to execute """ - worker = Worker(self, block, code) - # Change color to running block.run_state = ExecutableState.RUNNING + worker = Worker(self, block, code) worker.signals.stdout.connect(block.handle_stdout) worker.signals.image.connect(block.handle_image) worker.signals.finished.connect(self.run_queue) From 532b9ab38108a49e0f9afb44e979e3d05f5d4964 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Math=C3=AFs=20F=C3=A9d=C3=A9rico?= Date: Wed, 16 Feb 2022 23:37:50 +0100 Subject: [PATCH 04/19] :wrench: Update mnist example --- examples/mnist.ipyg | 126 ++++++++++++-------------------------------- 1 file changed, 35 insertions(+), 91 deletions(-) diff --git a/examples/mnist.ipyg b/examples/mnist.ipyg index 98f53d36..fe6cde13 100644 --- a/examples/mnist.ipyg +++ b/examples/mnist.ipyg @@ -6,15 +6,15 @@ "title": "Load MNIST dataset", "block_type": "CodeBlock", "splitter_pos": [ - 137, + 158, 0 ], "position": [ - -211.7736206054691, - -75.79580688476543 + -284.09727591359615, + -81.65988704488383 ], - "width": 618, - "height": 184, + "width": 775, + "height": 211, "metadata": { "title_metadata": { "color": "white", @@ -23,26 +23,12 @@ } }, "sockets": [ - { - "id": 2039122756520, - "type": "input", - "position": [ - 309.0, - 0.0 - ], - "metadata": { - "color": "#FF55FFF0", - "linecolor": "#FF000000", - "linewidth": 1.0, - "radius": 10.0 - } - }, { "id": 2039122756664, "type": "output", "position": [ - 309.0, - 184.0 + 387.5, + 211.0 ], "metadata": { "color": "#FF55FFF0", @@ -60,12 +46,12 @@ "title": "Evaluation", "block_type": "CodeBlock", "splitter_pos": [ - 78, - 78 + 75, + 75 ], "position": [ - 147.74591064453216, - 1429.3799743652335 + 325.8709106445323, + 1434.0674743652337 ], "width": 909, "height": 203, @@ -90,24 +76,10 @@ "linewidth": 1.0, "radius": 10.0 } - }, - { - "id": 2039123154840, - "type": "output", - "position": [ - 454.5, - 203.0 - ], - "metadata": { - "color": "#FF55FFF0", - "linecolor": "#FF000000", - "linewidth": 1.0, - "radius": 10.0 - } } ], "source": "metrics = model.evaluate(x_test, y_test)\r\nprint(f\"mean_loss:{metrics[0]:.2f}, mean_acc:{metrics[1]:.2f}\")", - "stdout": "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\r313/313 [==============================] - 1s 3ms/step - loss: 0.0562 - accuracy: 0.9831\nmean_loss:0.06, mean_acc:0.98\n" + "stdout": "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\r313/313 [==============================] - 1s 2ms/step - loss: 0.0507 - accuracy: 0.9830\nmean_loss:0.05, mean_acc:0.98\n" }, { "id": 2039123177336, @@ -115,11 +87,11 @@ "block_type": "CodeBlock", "splitter_pos": [ 0, - 287 + 281 ], "position": [ - -213.56268310546818, - 1421.7393493652344 + -68.25018310546821, + 1406.1143493652346 ], "width": 323, "height": 334, @@ -144,36 +116,22 @@ "linewidth": 1.0, "radius": 10.0 } - }, - { - "id": 2039123391544, - "type": "output", - "position": [ - 161.5, - 334.0 - ], - "metadata": { - "color": "#FF55FFF0", - "linecolor": "#FF000000", - "linewidth": 1.0, - "radius": 10.0 - } } ], "source": "rd_index = np.random.randint(len(x_test))\r\nprediction = np.argmax(model.predict(x_test[rd_index].reshape(1, 28, 28, 1)))\r\nplt.imshow(x_test[rd_index], cmap='gray')\r\nplt.title(\"Predicted: \" + str(prediction))", - "stdout": "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\n" + "stdout": "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\n" }, { "id": 2039123183800, "title": "Training", "block_type": "CodeBlock", "splitter_pos": [ - 87, - 233 + 85, + 229 ], "position": [ - 1.179504394530909, - 934.3252868652344 + -30.07049560546909, + 956.2002868652345 ], "width": 1049, "height": 367, @@ -215,22 +173,22 @@ } ], "source": "model.fit(x=x_train,y=y_train, epochs=4)\r\n", - "stdout": "Epoch 1/4\n\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\r1875/1875 [==============================] - 9s 3ms/step - loss: 0.3597 - accuracy: 0.8903\nEpoch 2/4\n\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\r1875/1875 [==============================] - 7s 4ms/step - loss: 0.0926 - accuracy: 0.9717\nEpoch 3/4\n\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\r1875/1875 [==============================] - 7s 4ms/step - loss: 0.0592 - accuracy: 0.9813\nEpoch 4/4\n\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\r1875/1875 [==============================] - 7s 4ms/step - loss: 0.0466 - accuracy: 0.9853\n" + "stdout": "Epoch 1/4\n\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\r1875/1875 [==============================] - 9s 3ms/step - loss: 0.2107 - accuracy: 0.9372\nEpoch 2/4\n\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\r1875/1875 [==============================] - 6s 3ms/step - loss: 0.0834 - accuracy: 0.9746\nEpoch 3/4\n\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\r1875/1875 [==============================] - 6s 3ms/step - loss: 0.0593 - accuracy: 0.9818\nEpoch 4/4\n\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\r1875/1875 [==============================] - 6s 3ms/step - loss: 0.0469 - accuracy: 0.9848\n" }, { "id": 2039123243512, "title": "Build Keras CNN", "block_type": "CodeBlock", "splitter_pos": [ - 259, + 395, 0 ], "position": [ - 25.03887939453091, - 520.9541931152345 + 3.663879394530909, + 441.70419311523443 ], - "width": 580, - "height": 306, + "width": 984, + "height": 448, "metadata": { "title_metadata": { "color": "white", @@ -243,7 +201,7 @@ "id": 2039123244520, "type": "input", "position": [ - 290.0, + 492.0, 0.0 ], "metadata": { @@ -257,8 +215,8 @@ "id": 2039123244664, "type": "output", "position": [ - 290.0, - 306.0 + 492.0, + 448.0 ], "metadata": { "color": "#FF55FFF0", @@ -277,7 +235,7 @@ "block_type": "CodeBlock", "splitter_pos": [ 0, - 283 + 277 ], "position": [ -413.21112060546864, @@ -306,36 +264,22 @@ "linewidth": 1.0, "radius": 10.0 } - }, - { - "id": 2039171275080, - "type": "output", - "position": [ - 160.0, - 330.0 - ], - "metadata": { - "color": "#FF55FFF0", - "linecolor": "#FF000000", - "linewidth": 1.0, - "radius": 10.0 - } } ], "source": "# Display an example from the dataset\r\nrd_index = np.random.randint(len(x_train))\r\nplt.imshow(x_train[rd_index], cmap='gray')\r\nplt.title('Class '+ str(y_train[rd_index]))\r\n", - "stdout": "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\n" + "stdout": "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\n" }, { "id": 2039171312808, "title": "Normalize dataset", "block_type": "CodeBlock", "splitter_pos": [ - 76, - 76 + 73, + 73 ], "position": [ - 6.476379394530795, - 216.01669311523463 + 186.16387939453068, + 183.20419311523463 ], "width": 619, "height": 199, From dbd10735e4f60f094c1554e511a455e6296e9819 Mon Sep 17 00:00:00 2001 From: Fabien Roger Date: Fri, 18 Feb 2022 00:01:47 +0100 Subject: [PATCH 05/19] :art: Make the outline more visible --- pyflow/blocks/block.py | 16 ++++++++++------ pyflow/blocks/codeblock.py | 2 ++ 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/pyflow/blocks/block.py b/pyflow/blocks/block.py index ce4e4f97..49f00498 100644 --- a/pyflow/blocks/block.py +++ b/pyflow/blocks/block.py @@ -72,8 +72,11 @@ def __init__( self.sockets_in: List[Socket] = [] self.sockets_out: List[Socket] = [] + self.pen_width = 3 self._pen_outline = QPen(QColor("#00000000")) + self._pen_outline.setWidth(self.pen_width) self._pen_outline_selected = QPen(QColor("#800030FF")) + self._pen_outline_selected.setWidth(self.pen_width) self._brush_background = QBrush(BACKGROUND_COLOR) self.setFlag(QGraphicsItem.GraphicsItemFlag.ItemIsSelectable) @@ -138,6 +141,7 @@ def paint( path_outline.addRoundedRect( 0, 0, self.width, self.height, self.edge_size, self.edge_size ) + pen = self.pen_outline painter.setPen(self.pen_outline) painter.setBrush(Qt.BrushStyle.NoBrush) painter.drawPath(path_outline.simplified()) @@ -147,12 +151,12 @@ def paint( path_in_outline = QPainterPath() outline_width = self.pen_outline.widthF() path_in_outline.addRoundedRect( - 2 * outline_width, - 2 * outline_width, - self.width - 4 * outline_width, - self.height - 4 * outline_width, - self.edge_size - 2 * outline_width, - self.edge_size - 2 * outline_width, + -2 * outline_width, + -2 * outline_width, + self.width + 4 * outline_width, + self.height + 4 * outline_width, + self.edge_size + 2 * outline_width, + self.edge_size + 2 * outline_width, ) painter.setPen(self._pen_outline_selected) painter.setBrush(Qt.BrushStyle.NoBrush) diff --git a/pyflow/blocks/codeblock.py b/pyflow/blocks/codeblock.py index 1de87ab0..fcbf2ec0 100644 --- a/pyflow/blocks/codeblock.py +++ b/pyflow/blocks/codeblock.py @@ -71,6 +71,8 @@ def __init__(self, source: str = "", **kwargs): ExecutableState.DONE: QPen(QColor("#158000")), # Dark green ExecutableState.CRASHED: QPen(QColor("#ff0000")), # Red: Crashed } + for pen in self._pen_outlines.values(): + pen.setWidth(self.pen_width) self.output_panel_background_color = "#1E1E1E" From cad6fa2687b490642bab3372939654b5ed83c5c5 Mon Sep 17 00:00:00 2001 From: Fabien Roger Date: Fri, 18 Feb 2022 00:09:02 +0100 Subject: [PATCH 06/19] :beetle: Don't mark blocks as executed after execution was canceled --- pyflow/blocks/codeblock.py | 7 +++++++ pyflow/blocks/executableblock.py | 10 ++++++++-- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/pyflow/blocks/codeblock.py b/pyflow/blocks/codeblock.py index fcbf2ec0..ebc0917d 100644 --- a/pyflow/blocks/codeblock.py +++ b/pyflow/blocks/codeblock.py @@ -173,10 +173,17 @@ def run_code(self): super().run_code() # actually run the code def execution_finished(self): + """Reset the text of the run buttons after it was executed.""" super().execution_finished() self.run_button.setText(">") self.run_all_button.setText(">>") + def execution_canceled(self): + """Reset the text of the run buttons after it was canceled.""" + super().execution_canceled() + self.run_button.setText(">") + self.run_all_button.setText(">>") + def link(self, block: "ExecutableBlock"): """Link a block to the current one.""" # Add sockets to the new block and the current one diff --git a/pyflow/blocks/executableblock.py b/pyflow/blocks/executableblock.py index 462afdfd..1a1ea49e 100644 --- a/pyflow/blocks/executableblock.py +++ b/pyflow/blocks/executableblock.py @@ -87,16 +87,22 @@ def run_code(self): self.run_state = ExecutableState.PENDING def execution_finished(self): - """Reset the text of the run buttons.""" + """Reset the state of the block after it was executed.""" if self.run_state != ExecutableState.CRASHED: self.run_state = ExecutableState.DONE self.blocks_to_run = [] + def execution_canceled(self): + """Reset the state of the block after its execution was canceled.""" + if self.run_state != ExecutableState.CRASHED: + self.run_state = ExecutableState.IDLE + self.blocks_to_run = [] + def _interrupt_execution(self): """Interrupt an execution, reset the blocks in the queue.""" for block, _ in self.scene().kernel.execution_queue: # Reset the blocks that have not been run - block.execution_finished() + block.execution_canceled() # Clear kernel execution queue self.scene().kernel.execution_queue = [] # Interrupt the kernel From 2665ce21586d77f67bc0e8f13ace0216a1faefed Mon Sep 17 00:00:00 2001 From: Fabien Roger Date: Fri, 18 Feb 2022 00:10:56 +0100 Subject: [PATCH 07/19] :sparkles: Correct typing --- pyflow/core/kernel.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pyflow/core/kernel.py b/pyflow/core/kernel.py index 217236ad..d4915b21 100644 --- a/pyflow/core/kernel.py +++ b/pyflow/core/kernel.py @@ -3,6 +3,7 @@ """ Module to create and manage ipython kernels.""" +from ast import Str import queue from typing import TYPE_CHECKING, List, Tuple from jupyter_client.manager import start_new_kernel @@ -24,7 +25,7 @@ class Kernel: @log_init_time(LOGGER) def __init__(self): self.kernel_manager, self.client = start_new_kernel() - self.execution_queue: List["ExecutableBlock"] = [] + self.execution_queue: List[Tuple["ExecutableBlock", str]] = [] self.busy = False def message_to_output(self, message: dict) -> Tuple[str, str]: From 51d8c6d3dc9ad2ec8abbfa8e818cbf4b0eecb3e5 Mon Sep 17 00:00:00 2001 From: Fabien Roger Date: Fri, 18 Feb 2022 00:16:06 +0100 Subject: [PATCH 08/19] :sparkles: Remove useless import --- pyflow/core/kernel.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pyflow/core/kernel.py b/pyflow/core/kernel.py index d4915b21..93e2f886 100644 --- a/pyflow/core/kernel.py +++ b/pyflow/core/kernel.py @@ -3,7 +3,6 @@ """ Module to create and manage ipython kernels.""" -from ast import Str import queue from typing import TYPE_CHECKING, List, Tuple from jupyter_client.manager import start_new_kernel From e4042616b0946de82a98db752df6f67d582012cc Mon Sep 17 00:00:00 2001 From: Fabien Roger Date: Fri, 18 Feb 2022 01:00:21 +0100 Subject: [PATCH 09/19] :beetle: Fix multiple execution bug --- pyflow/blocks/executableblock.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/pyflow/blocks/executableblock.py b/pyflow/blocks/executableblock.py index 1a1ea49e..9e97998e 100644 --- a/pyflow/blocks/executableblock.py +++ b/pyflow/blocks/executableblock.py @@ -265,7 +265,11 @@ def right_traversal(self): def run_blocks(self): """Run a list of blocks.""" for block in self.blocks_to_run[::-1] + [self]: - if block.run_state != ExecutableState.DONE: + if block.run_state not in { + ExecutableState.PENDING, + ExecutableState.RUNNING, + ExecutableState.DONE, + }: block.run_code() def run_left(self): From 4d01d8d2c1067781d13971d5fe4c3fa45107994b Mon Sep 17 00:00:00 2001 From: Fabien Roger Date: Fri, 18 Feb 2022 01:02:58 +0100 Subject: [PATCH 10/19] :sparkles: Remove unused variable --- pyflow/blocks/block.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pyflow/blocks/block.py b/pyflow/blocks/block.py index 49f00498..3c2887b0 100644 --- a/pyflow/blocks/block.py +++ b/pyflow/blocks/block.py @@ -141,7 +141,6 @@ def paint( path_outline.addRoundedRect( 0, 0, self.width, self.height, self.edge_size, self.edge_size ) - pen = self.pen_outline painter.setPen(self.pen_outline) painter.setBrush(Qt.BrushStyle.NoBrush) painter.drawPath(path_outline.simplified()) From ceee34547bb52b5426ff8d0e39eadf3237881b07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Math=C3=AFs=20F=C3=A9d=C3=A9rico?= Date: Sat, 19 Feb 2022 13:12:03 +0100 Subject: [PATCH 11/19] :art: Changed selection and running/pending colors --- pyflow/blocks/block.py | 2 +- pyflow/blocks/codeblock.py | 4 ++-- pyflow/core/edge.py | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/pyflow/blocks/block.py b/pyflow/blocks/block.py index 3c2887b0..fe53fede 100644 --- a/pyflow/blocks/block.py +++ b/pyflow/blocks/block.py @@ -75,7 +75,7 @@ def __init__( self.pen_width = 3 self._pen_outline = QPen(QColor("#00000000")) self._pen_outline.setWidth(self.pen_width) - self._pen_outline_selected = QPen(QColor("#800030FF")) + self._pen_outline_selected = QPen(QColor("#FFFFA637")) self._pen_outline_selected.setWidth(self.pen_width) self._brush_background = QBrush(BACKGROUND_COLOR) diff --git a/pyflow/blocks/codeblock.py b/pyflow/blocks/codeblock.py index ebc0917d..bdaa1dee 100644 --- a/pyflow/blocks/codeblock.py +++ b/pyflow/blocks/codeblock.py @@ -66,8 +66,8 @@ def __init__(self, source: str = "", **kwargs): self._pen_outlines = { ExecutableState.IDLE: QPen(QColor("#00000000")), # No outline - ExecutableState.RUNNING: QPen(QColor("#ff6107ff")), # Purple - ExecutableState.PENDING: QPen(QColor("#80fc6107")), # Dark orange + ExecutableState.RUNNING: QPen(QColor("#fffc6107")), # Dark orange + ExecutableState.PENDING: QPen(QColor("#30fc6107")), # Transparent orange ExecutableState.DONE: QPen(QColor("#158000")), # Dark green ExecutableState.CRASHED: QPen(QColor("#ff0000")), # Red: Crashed } diff --git a/pyflow/core/edge.py b/pyflow/core/edge.py index db44ae4a..4c1d4bb5 100644 --- a/pyflow/core/edge.py +++ b/pyflow/core/edge.py @@ -29,10 +29,10 @@ class Edge(QGraphicsPathItem, Serializable, Executable): def __init__( self, - edge_width: float = 6.0, + edge_width: float = 5.0, path_type=DEFAULT_DATA["path_type"], edge_color="#001000", - edge_selected_color="#0030FF", + edge_selected_color="#FFA637", edge_running_color="#FF0000", edge_pending_color="#00ff00", source: QPointF = QPointF(0, 0), From 63beaf0c34d768a5cc025a42b1ccbe8ea4e93c7c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Math=C3=AFs=20F=C3=A9d=C3=A9rico?= Date: Sat, 19 Feb 2022 13:20:59 +0100 Subject: [PATCH 12/19] :beetle: Run right now relauch next blocks even if they were done --- pyflow/blocks/executableblock.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pyflow/blocks/executableblock.py b/pyflow/blocks/executableblock.py index 9e97998e..e5bc9645 100644 --- a/pyflow/blocks/executableblock.py +++ b/pyflow/blocks/executableblock.py @@ -312,6 +312,8 @@ def run_right(self): # For each output found for block in self.blocks_to_run.copy()[::-1]: # Gather dependencies + if block is not self: + block.run_state = ExecutableState.IDLE new_blocks_to_run, _ = self.custom_bfs(block) self.blocks_to_run += new_blocks_to_run From f145bd0aaee20eefe1714cac7b81c273bcd74545 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Math=C3=AFs=20F=C3=A9d=C3=A9rico?= Date: Sat, 19 Feb 2022 13:24:55 +0100 Subject: [PATCH 13/19] :memo: Add executable docstrings --- pyflow/core/executable.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/pyflow/core/executable.py b/pyflow/core/executable.py index 91f8f874..4e6369c3 100644 --- a/pyflow/core/executable.py +++ b/pyflow/core/executable.py @@ -1,7 +1,14 @@ +# Pyflow an open-source tool for modular visual programing in python +# Copyright (C) 2021-2022 Bycelium + +""" Module for the Executable abstract class.""" + from enum import Enum class ExecutableState(Enum): + """Enumeration of possible states of an Executable.""" + IDLE = 0 RUNNING = 1 PENDING = 2 @@ -10,7 +17,10 @@ class ExecutableState(Enum): class Executable: + """Executable object in pyflow.""" + def __init__(self) -> None: + """Executable object in pyflow.""" self._run_state = ExecutableState.IDLE @property From 56ddc56cd9e01adb8fdec22d14546d3cd3b87d9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Math=C3=AFs=20F=C3=A9d=C3=A9rico?= Date: Sat, 19 Feb 2022 13:28:30 +0100 Subject: [PATCH 14/19] :sparkles: Fix pylint issues --- pyflow/blocks/executableblock.py | 6 +++--- pyflow/core/add_button.py | 5 +++++ 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/pyflow/blocks/executableblock.py b/pyflow/blocks/executableblock.py index e5bc9645..c37e2447 100644 --- a/pyflow/blocks/executableblock.py +++ b/pyflow/blocks/executableblock.py @@ -147,7 +147,7 @@ def custom_bfs(self, start_node, reverse=False): # Blocks to run in topological order blocks_to_run: List["ExecutableBlock"] = [] # List of lists of blocks/edges to animate in order - to_transmit: List[List[Union["ExecutableBlock", "Edge"]]] = [[start_node]] + to_transmit: List[List[Union["ExecutableBlock", Edge]]] = [[start_node]] to_visit: List["ExecutableBlock"] = [start_node] while to_visit: @@ -200,7 +200,7 @@ def right_traversal(self): list: each element is a list of blocks/edges to animate in order """ # Result - to_transmit: List[List[Union["ExecutableBlock", "Edge"]]] = [[self]] + to_transmit: List[List[Union["ExecutableBlock", Edge]]] = [[self]] # To check if a block has been visited visited: Set["ExecutableBlock"] = set([]) @@ -210,7 +210,7 @@ def right_traversal(self): to_visit_output: Set["ExecutableBlock"] = set([self]) # Next stage to put in to_transmit - next_edges: List["Edge"] = [] + next_edges: List[Edge] = [] next_blocks: List["ExecutableBlock"] = [] while to_visit_input or to_visit_output: diff --git a/pyflow/core/add_button.py b/pyflow/core/add_button.py index 9b627d16..d28ee21e 100644 --- a/pyflow/core/add_button.py +++ b/pyflow/core/add_button.py @@ -45,6 +45,11 @@ def __init__( self.setAcceptHoverEvents(True) def set_highlight(self, value: bool) -> None: + """Set the AddButton highlight to the given boolean value. + + Args: + value (bool): New highlight value. + """ if value: self._brush = self._block_hover_brush else: From 746c450da961b1cbc7b41cfd7c5a8857ae91be4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Math=C3=AFs=20F=C3=A9d=C3=A9rico?= Date: Sat, 19 Feb 2022 13:34:29 +0100 Subject: [PATCH 15/19] :wrench: Refactor custom_bfs --- pyflow/blocks/executableblock.py | 45 ++++++++++++++++---------------- 1 file changed, 22 insertions(+), 23 deletions(-) diff --git a/pyflow/blocks/executableblock.py b/pyflow/blocks/executableblock.py index c37e2447..ff571c46 100644 --- a/pyflow/blocks/executableblock.py +++ b/pyflow/blocks/executableblock.py @@ -144,46 +144,45 @@ def custom_bfs(self, start_node, reverse=False): list: Blocks to run in topological order (reversed) list: each element is a list of blocks/edges to animate in order """ + + def gather_edges_to_visit(sockets: List[Socket]): + edges_to_visit = [] + for socket in sockets: + for edge in socket.edges: + if edge.source_socket.is_on and edge.destination_socket.is_on: + edges_to_visit.append(edge) + return edges_to_visit + # Blocks to run in topological order blocks_to_run: List["ExecutableBlock"] = [] # List of lists of blocks/edges to animate in order to_transmit: List[List[Union["ExecutableBlock", Edge]]] = [[start_node]] - to_visit: List["ExecutableBlock"] = [start_node] - while to_visit: + blocks_to_visit: List["ExecutableBlock"] = [start_node] + while blocks_to_visit: # Remove duplicates - to_visit = list(set(to_visit)) + blocks_to_visit = list(set(blocks_to_visit)) # Gather connected edges - edges_to_visit = [] - for block in to_visit: + edges_to_visit: List[Edge] = [] + for block in blocks_to_visit: blocks_to_run.append(block) if not reverse: - for input_socket in block.sockets_in: - for edge in input_socket.edges: - if ( - edge.source_socket.is_on - and edge.destination_socket.is_on - ): - edges_to_visit.append(edge) + next_sockets = block.sockets_in else: - for output_socket in block.sockets_out: - for edge in output_socket.edges: - if ( - edge.source_socket.is_on - and edge.destination_socket.is_on - ): - edges_to_visit.append(edge) + next_sockets = block.sockets_out + edges_to_visit += gather_edges_to_visit(next_sockets) to_transmit.append(edges_to_visit) # Gather connected blocks - to_visit = [] + blocks_to_visit = [] for edge in edges_to_visit: if not reverse: - to_visit.append(edge.source_socket.block) + next_blocks = edge.source_socket.block else: - to_visit.append(edge.destination_socket.block) - to_transmit.append(to_visit) + next_blocks = edge.destination_socket.block + blocks_to_visit.append(next_blocks) + to_transmit.append(blocks_to_visit) # Remove start node blocks_to_run.pop(0) From 3fbfc685e293ce3701d3211e1ef3834178e0a248 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Math=C3=AFs=20F=C3=A9d=C3=A9rico?= Date: Sat, 19 Feb 2022 13:49:16 +0100 Subject: [PATCH 16/19] :wrench: Refactor right_traversal --- pyflow/blocks/executableblock.py | 74 +++++++++++++++++++------------- 1 file changed, 43 insertions(+), 31 deletions(-) diff --git a/pyflow/blocks/executableblock.py b/pyflow/blocks/executableblock.py index ff571c46..bc1abf34 100644 --- a/pyflow/blocks/executableblock.py +++ b/pyflow/blocks/executableblock.py @@ -198,6 +198,39 @@ def right_traversal(self): Returns: list: each element is a list of blocks/edges to animate in order """ + + def gather_next_blocks( + sockets: List[Socket], + visited: Set[Union[Block, Edge]] = None, + to_visit: Set[Block] = None, + ): + visited = [] if visited is None else visited + to_visit = [] if to_visit is None else to_visit + + next_blocks = [] + next_edges = [] + + for socket in sockets: + for edge in socket.edges: + if not ( + edge not in visited + and edge.source_socket.is_on + and edge.destination_socket.is_on + ): + continue + + next_edges.append(edge) + visited.add(edge) + + next_block = edge.destination_socket.block + to_visit.add(next_block) + visited.add(next_block) + + if next_block not in visited: + next_blocks.append(next_block) + + return next_blocks, next_edges + # Result to_transmit: List[List[Union["ExecutableBlock", Edge]]] = [[self]] @@ -215,40 +248,19 @@ def right_traversal(self): while to_visit_input or to_visit_output: for block in to_visit_input.copy(): # Check input edges and blocks - for input_socket in block.sockets_in: - for edge in input_socket.edges: - if not ( - edge not in visited - and edge.source_socket.is_on - and edge.destination_socket.is_on - ): - continue - next_edges.append(edge) - visited.add(edge) - input_block = edge.source_socket.block - to_visit_input.add(input_block) - if input_block not in visited: - next_blocks.append(input_block) - visited.add(input_block) + new_blocks, new_edges = gather_next_blocks( + block.sockets_in, visited, to_visit_input + ) + next_blocks += new_blocks + next_edges += new_edges to_visit_input.remove(block) for block in to_visit_output.copy(): # Check output edges and blocks - for output_socket in block.sockets_out: - for edge in output_socket.edges: - if not ( - edge not in visited - and edge.source_socket.is_on - and edge.destination_socket.is_on - ): - continue - next_edges.append(edge) - visited.add(edge) - output_block = edge.destination_socket.block - to_visit_input.add(output_block) - to_visit_output.add(output_block) - if output_block not in visited: - next_blocks.append(output_block) - visited.add(output_block) + new_blocks, new_edges = gather_next_blocks( + block.sockets_out, visited, to_visit_output + ) + next_blocks += new_blocks + next_edges += new_edges to_visit_output.remove(block) # Add the next stage to to_transmit From 0d58e55e88b6a4f24a5b70c5de540a1cb4eb835b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Math=C3=AFs=20F=C3=A9d=C3=A9rico?= Date: Sat, 19 Feb 2022 14:09:45 +0100 Subject: [PATCH 17/19] :memo: Add missing docstrings --- pyflow/blocks/executableblock.py | 28 +++++++++++++++++++++++----- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/pyflow/blocks/executableblock.py b/pyflow/blocks/executableblock.py index 1714f8e5..9a6ef787 100644 --- a/pyflow/blocks/executableblock.py +++ b/pyflow/blocks/executableblock.py @@ -146,6 +146,14 @@ def custom_bfs(self, start_node, reverse=False): """ def gather_edges_to_visit(sockets: List[Socket]): + """Get list of next edges to visit given a list of sockets. + + Args: + sockets (List[Socket]): List of sockets to search edges on. + + Returns: + List[Edge]: List of edges connected to sockets given. + """ edges_to_visit = [] for socket in sockets: for edge in socket.edges: @@ -210,13 +218,23 @@ def right_traversal(self): list: each element is a list of blocks/edges to animate in order """ - def gather_next_blocks( + def gather_next_blocks_and_edges( sockets: List[Socket], visited: Set[Union[Block, Edge]] = None, to_visit: Set[Block] = None, ): - visited = [] if visited is None else visited - to_visit = [] if to_visit is None else to_visit + """Gather next blocks and edges to run given a list of sockets. + + Args: + sockets (List[Socket]): List of sockets to search next blocks and edges on. + visited (Set[Union[Block, Edge]], optional): Already visited blocks and edges. Defaults to None. + to_visit (Set[Block], optional): List of next blocks to visit. Defaults to None. + + Returns: + Tuple[List[Block], List[Edge]]: Lists of next blocks and next edges to run. + """ + visited = set() if visited is None else visited + to_visit = set() if to_visit is None else to_visit next_blocks = [] next_edges = [] @@ -259,7 +277,7 @@ def gather_next_blocks( while to_visit_input or to_visit_output: for block in to_visit_input.copy(): # Check input edges and blocks - new_blocks, new_edges = gather_next_blocks( + new_blocks, new_edges = gather_next_blocks_and_edges( block.sockets_in, visited, to_visit_input ) next_blocks += new_blocks @@ -267,7 +285,7 @@ def gather_next_blocks( to_visit_input.remove(block) for block in to_visit_output.copy(): # Check output edges and blocks - new_blocks, new_edges = gather_next_blocks( + new_blocks, new_edges = gather_next_blocks_and_edges( block.sockets_out, visited, to_visit_output ) next_blocks += new_blocks From 25a5c0555db17f65a253ac033a5e37525d3dcca7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Math=C3=AFs=20F=C3=A9d=C3=A9rico?= Date: Sat, 19 Feb 2022 14:14:44 +0100 Subject: [PATCH 18/19] :sparkles: Fix some pylint issues --- pyflow/blocks/executableblock.py | 3 ++- pyflow/graphics/view.py | 6 ++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/pyflow/blocks/executableblock.py b/pyflow/blocks/executableblock.py index 9a6ef787..5b63c214 100644 --- a/pyflow/blocks/executableblock.py +++ b/pyflow/blocks/executableblock.py @@ -227,7 +227,8 @@ def gather_next_blocks_and_edges( Args: sockets (List[Socket]): List of sockets to search next blocks and edges on. - visited (Set[Union[Block, Edge]], optional): Already visited blocks and edges. Defaults to None. + visited (Set[Union[Block, Edge]], optional): Already visited blocks and edges. + Defaults to None. to_visit (Set[Block], optional): List of next blocks to visit. Defaults to None. Returns: diff --git a/pyflow/graphics/view.py b/pyflow/graphics/view.py index 677280bd..e9832b7a 100644 --- a/pyflow/graphics/view.py +++ b/pyflow/graphics/view.py @@ -510,8 +510,10 @@ def drag_edge(self, event: QMouseEvent, action="press"): ): # Link a new CodeBlock under the selected block parent: CodeBlock = item_at_click.block - empty_code_block_path: str = os.path.join(BLOCKFILES_PATH, "empty.pfb") - new_block = self.scene().create_block_from_file(empty_code_block_path, 0, 0) + empty_code_block_path: str = os.path.join(BLOCKFILES_PATH, "empty.pfb") + new_block = self.scene().create_block_from_file( + empty_code_block_path, 0, 0 + ) parent.link_and_place(new_block) scene.history.checkpoint( "Created a new linked block", set_modified=True From 0d78e9635064ba75d9dfc2807e04a23b12cac313 Mon Sep 17 00:00:00 2001 From: Fabien Roger Date: Sat, 19 Feb 2022 14:25:08 +0100 Subject: [PATCH 19/19] :art: Make the pending outline more visible --- pyflow/blocks/codeblock.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyflow/blocks/codeblock.py b/pyflow/blocks/codeblock.py index bdaa1dee..f5fa476f 100644 --- a/pyflow/blocks/codeblock.py +++ b/pyflow/blocks/codeblock.py @@ -67,7 +67,7 @@ def __init__(self, source: str = "", **kwargs): self._pen_outlines = { ExecutableState.IDLE: QPen(QColor("#00000000")), # No outline ExecutableState.RUNNING: QPen(QColor("#fffc6107")), # Dark orange - ExecutableState.PENDING: QPen(QColor("#30fc6107")), # Transparent orange + ExecutableState.PENDING: QPen(QColor("#90fc6107")), # Transparent orange ExecutableState.DONE: QPen(QColor("#158000")), # Dark green ExecutableState.CRASHED: QPen(QColor("#ff0000")), # Red: Crashed }