From ee02258499daee88557ae4171dd076b53a7017ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Del=C3=A8gue?= Date: Sat, 11 Dec 2021 21:35:29 +0100 Subject: [PATCH 01/22] =?UTF-8?q?=F0=9F=94=A8=20Add=20the=20concept=20of?= =?UTF-8?q?=20an=20executable=20block=20as=20not=20all=20block=20can=20be?= =?UTF-8?q?=20executed.=20This=20will=20be=20used=20to=20make=20sliders=20?= =?UTF-8?q?"executable".?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- opencodeblocks/blocks/__init__.py | 8 +- opencodeblocks/blocks/block.py | 4 - opencodeblocks/blocks/codeblock.py | 113 +------------ opencodeblocks/blocks/containerblock.py | 86 ++++++++++ opencodeblocks/blocks/executableblock.py | 201 +++++++++++++++++++++++ opencodeblocks/blocks/sliderblock.py | 21 ++- opencodeblocks/graphics/kernel.py | 15 +- opencodeblocks/graphics/pyeditor.py | 11 +- opencodeblocks/graphics/worker.py | 1 - 9 files changed, 334 insertions(+), 126 deletions(-) create mode 100644 opencodeblocks/blocks/containerblock.py create mode 100644 opencodeblocks/blocks/executableblock.py diff --git a/opencodeblocks/blocks/__init__.py b/opencodeblocks/blocks/__init__.py index 82fed7bc..93e87f76 100644 --- a/opencodeblocks/blocks/__init__.py +++ b/opencodeblocks/blocks/__init__.py @@ -3,8 +3,14 @@ """ Module for the OCB Blocks of different types. """ -from opencodeblocks.blocks.sliderblock import OCBSliderBlock +# Abstract blocks from opencodeblocks.blocks.block import OCBBlock +from opencodeblocks.blocks.executableblock import OCBExecutableBlock + +# Real blocks +from opencodeblocks.blocks.sliderblock import OCBSliderBlock from opencodeblocks.blocks.codeblock import OCBCodeBlock from opencodeblocks.blocks.markdownblock import OCBMarkdownBlock from opencodeblocks.blocks.drawingblock import OCBDrawingBlock + + diff --git a/opencodeblocks/blocks/block.py b/opencodeblocks/blocks/block.py index 569d97b1..f714b94f 100644 --- a/opencodeblocks/blocks/block.py +++ b/opencodeblocks/blocks/block.py @@ -33,7 +33,6 @@ class OCBBlock(QGraphicsItem, Serializable): def __init__( self, block_type: str = "base", - source: str = "", position: tuple = (0, 0), width: int = 300, height: int = 200, @@ -45,7 +44,6 @@ def __init__( Args: block_type: Block type. - source: Block source text. position: Block position in the scene. width: Block width. height: Block height. @@ -58,8 +56,6 @@ def __init__( Serializable.__init__(self) self.block_type = block_type - self.source = source - self.stdout = "" self.setPos(QPointF(*position)) self.sockets_in = [] self.sockets_out = [] diff --git a/opencodeblocks/blocks/codeblock.py b/opencodeblocks/blocks/codeblock.py index ae3e6c21..01824e3c 100644 --- a/opencodeblocks/blocks/codeblock.py +++ b/opencodeblocks/blocks/codeblock.py @@ -9,14 +9,14 @@ from ansi2html import Ansi2HTMLConverter from networkx.algorithms.traversal.breadth_first_search import bfs_edges -from opencodeblocks.blocks.block import OCBBlock +from opencodeblocks.blocks.executableblock import OCBExecutableBlock from opencodeblocks.graphics.socket import OCBSocket from opencodeblocks.graphics.pyeditor import PythonEditor conv = Ansi2HTMLConverter() -class OCBCodeBlock(OCBBlock): +class OCBCodeBlock(OCBExecutableBlock): """ Code Block @@ -33,10 +33,10 @@ def __init__(self, **kwargs): Create a new OCBCodeBlock. Initialize all the child widgets specific to this block type """ + super().__init__(**kwargs) self.source_editor = PythonEditor(self) self._source = "" - - super().__init__(**kwargs) + self._stdout = "" self.output_panel_height = self.height / 3 self._min_output_panel_height = 20 @@ -44,16 +44,6 @@ def __init__(self, **kwargs): self.output_closed = True self._splitter_size = [1, 1] - self._cached_stdout = "" - self.has_been_run = False - - # Add exectution flow sockets - exe_sockets = ( - OCBSocket(self, socket_type="input", flow_type="exe"), - OCBSocket(self, socket_type="output", flow_type="exe"), - ) - for socket in exe_sockets: - self.add_socket(socket) # Add output pannel self.output_panel = self.init_output_panel() @@ -106,103 +96,14 @@ def run_code(self): self.run_button.setText("...") self.run_all_button.setText("...") - # Run code by adding to code to queue - code = self.source_editor.text() - self.source = code - kernel = self.source_editor.kernel - kernel.execution_queue.append((self, code)) - if kernel.busy is False: - kernel.run_queue() - self.has_been_run = True + super().run_code() # actually run the code - def reset_buttons(self): + def execution_finished(self): """Reset the text of the run buttons""" + super().execution_finished() self.run_button.setText(">") self.run_all_button.setText(">>") - def has_input(self) -> bool: - """Checks whether a block has connected input blocks""" - for input_socket in self.sockets_in: - if len(input_socket.edges) != 0: - return True - return False - - def has_output(self) -> bool: - """Checks whether a block has connected output blocks""" - for output_socket in self.sockets_out: - if len(output_socket.edges) != 0: - return True - return False - - def _interrupt_execution(self): - """ Interrupt an execution, reset the blocks in the queue """ - for block, _ in self.source_editor.kernel.execution_queue: - # Reset the blocks that have not been run - block.reset_buttons() - block.has_been_run = False - # Clear the queue - self.source_editor.kernel.execution_queue = [] - # Interrupt the kernel - self.source_editor.kernel.kernel_manager.interrupt_kernel() - - def run_left(self, in_right_button=False): - """ - Run all of the block's dependencies and then run the block - """ - # If the user presses left run when running, cancel the execution - if self.run_button.text() == "..." and not in_right_button: - self._interrupt_execution() - return - - # If no dependencies - if not self.has_input(): - return self.run_code() - - # Create the graph from the scene - graph = self.scene().create_graph() - # BFS through the input graph - edges = bfs_edges(graph, self, reverse=True) - # Run the blocks found except self - blocks_to_run: List["OCBCodeBlock"] = [v for _, v in edges] - for block in blocks_to_run[::-1]: - if not block.has_been_run: - block.run_code() - - if in_right_button: - # If run_left was called inside of run_right - # self is not necessarily the block that was clicked - # which means that self does not need to be run - if not self.has_been_run: - self.run_code() - else: - # On the contrary if run_left was called outside of run_right - # self is the block that was clicked - # so self needs to be run - self.run_code() - - def run_right(self): - """Run all of the output blocks and all their dependencies""" - # If the user presses right run when running, cancel the execution - if self.run_all_button.text() == "...": - self._interrupt_execution() - return - - # If no output, run left - if not self.has_output(): - return self.run_left(in_right_button=True) - - # Same as run_left but instead of running the blocks, we'll use run_left - graph = self.scene().create_graph() - edges = bfs_edges(graph, self) - blocks_to_run: List["OCBCodeBlock"] = [ - self] + [v for _, v in edges] - for block in blocks_to_run[::-1]: - block.run_left(in_right_button=True) - - def reset_has_been_run(self): - """ Reset has_been_run, is called when the output is an error """ - self.has_been_run = False - def update_title(self): """Change the geometry of the title widget""" self.title_widget.setGeometry( diff --git a/opencodeblocks/blocks/containerblock.py b/opencodeblocks/blocks/containerblock.py new file mode 100644 index 00000000..21505b78 --- /dev/null +++ b/opencodeblocks/blocks/containerblock.py @@ -0,0 +1,86 @@ +# OpenCodeBlock an open-source tool for modular visual programing in python + +""" +Exports OCBSliderBlock. +""" + +from typing import OrderedDict +from PyQt5.QtCore import Qt +from PyQt5.QtWidgets import QHBoxLayout, QLabel, QLineEdit, QSlider, QVBoxLayout +from opencodeblocks.blocks.block import OCBBlock + + +class OCBSliderBlock(OCBBlock): + """ + Features a slider ranging from 0 to 1 and an area to choose what value to assign the slider to. + """ + + def __init__(self, **kwargs): + super().__init__(**kwargs) + + self.layout = QVBoxLayout(self.root) + + self.slider = QSlider(Qt.Horizontal) + self.slider.valueChanged.connect(self.valueChanged) + + self.variable_layout = QHBoxLayout(self.root) + self.variable_text = QLineEdit("slider_value") + self.variable_value = QLabel(f"{self.slider.value()/100}") + + self.variable_text.setFixedWidth(self.root.width() / 2) + + self.variable_layout.addWidget(self.variable_text) + self.variable_layout.addWidget(self.variable_value) + + self.layout.setContentsMargins( + self.edge_size * 2, + self.title_widget.height() + self.edge_size * 2, + self.edge_size * 2, + self.edge_size * 2 + ) + self.layout.addWidget(self.slider) + self.layout.addLayout(self.variable_layout) + + self.holder.setWidget(self.root) + + def valueChanged(self): + """ This is called when the value of the slider changes """ + python_code = f"{self.var_name} = {self.value}" + self.variable_value.setText(f"{self.value}") + + # The code execution part will be added when the execution flow is merged. + # We print for now + print(python_code) + + @property + def value(self): + """ The value of the slider """ + return str(self.slider.value() / 100) + @value.setter + def value(self, value: str): + self.slider.setValue(int(float(value) * 100)) + + @property + def var_name(self): + """ The name of the python variable associated with the slider """ + return self.variable_text.text() + @var_name.setter + def var_name(self, value: str): + self.variable_text.setText(value) + + def serialize(self): + """ Return a serialized version of this widget """ + base_dict = super().serialize() + base_dict["value"] = self.value + base_dict["var_name"] = self.var_name + + return base_dict + + def deserialize(self, data: OrderedDict, + hashmap: dict = None, restore_id: bool = True): + """ Restore a slider block from it's serialized state """ + for dataname in ['value','var_name']: + if dataname in data: + setattr(self, dataname, data[dataname]) + + super().deserialize(data, hashmap, restore_id) diff --git a/opencodeblocks/blocks/executableblock.py b/opencodeblocks/blocks/executableblock.py new file mode 100644 index 00000000..4420abad --- /dev/null +++ b/opencodeblocks/blocks/executableblock.py @@ -0,0 +1,201 @@ +""" Module for the executable block class """ + +from typing import List, OrderedDict + +from ansi2html import Ansi2HTMLConverter +from networkx.algorithms.traversal.breadth_first_search import bfs_edges + +from opencodeblocks.blocks.block import OCBBlock +from opencodeblocks.graphics.socket import OCBSocket + +from opencodeblocks.graphics.kernel import get_main_kernel + +conv = Ansi2HTMLConverter() + + +class OCBExecutableBlock(OCBBlock): + + """ + Executable Block + + This block type is not meant to be instanciated ! + + It's an abstract class that represents blocks that can be executed like: + - OCBCodeBlock + - OCBSlider + + """ + + def __init__(self, **kwargs): + """ + Create a new executable block. + Do not call this method except when inheriting from this class. + """ + super().__init__(**kwargs) + + self.has_been_run = False + self.is_running = False + + # Add execution flow sockets + exe_sockets = ( + OCBSocket(self, socket_type="input", flow_type="exe"), + OCBSocket(self, socket_type="output", flow_type="exe"), + ) + for socket in exe_sockets: + self.add_socket(socket) + + if type(self) == OCBExecutableBlock: + raise RuntimeError("OCBExecutableBlock should not be instanciated directly") + + def has_input(self) -> bool: + """Checks whether a block has connected input blocks""" + for input_socket in self.sockets_in: + if len(input_socket.edges) != 0: + return True + return False + + def has_output(self) -> bool: + """Checks whether a block has connected output blocks""" + for output_socket in self.sockets_out: + if len(output_socket.edges) != 0: + return True + return False + + def run_code(self): + """ Run the code in the block""" + + # Queue the code to execute + code = self.source + kernel = get_main_kernel() + kernel.execution_queue.append((self, code)) + + self.is_running = True + + if kernel.busy is False: + kernel.run_queue() + self.has_been_run = True + + def execution_finished(self): + """ + Method called when the execution of the block is finished. + Implement the behavior you want here. + """ + self.is_running = False + + def _interrupt_execution(self): + """ Interrupt an execution, reset the blocks in the queue """ + kernel = get_main_kernel() + for block, _ in kernel.execution_queue: + # Reset the blocks that have not been run + block.execution_finished() + block.has_been_run = False + # Clear the queue + kernel.execution_queue = [] + # Interrupt the kernel + kernel.kernel_manager.interrupt_kernel() + + def has_input(self) -> bool: + """Checks whether a block has connected input blocks""" + for input_socket in self.sockets_in: + if len(input_socket.edges) != 0: + return True + return False + + def has_output(self) -> bool: + """Checks whether a block has connected output blocks""" + for output_socket in self.sockets_out: + if len(output_socket.edges) != 0: + return True + return False + + def run_left(self, in_run_right=False): + """ + Run all of the block's dependencies and then run the block + """ + # If the block was already running, cancel the execution + if self.is_running and not in_run_right: + self._interrupt_execution() + return + + # If no dependencies + if not self.has_input(): + return self.run_code() + + # Create the graph from the scene + graph = self.scene().create_graph() + # BFS through the input graph + edges = bfs_edges(graph, self, reverse=True) + # Run the blocks found except self + blocks_to_run: List["OCBExecutableblock"] = [v for _, v in edges] + for block in blocks_to_run[::-1]: + if not block.has_been_run: + block.run_code() + + if in_run_right: + # If run_left was called inside of run_right + # self is not necessarily the block that was clicked + # which means that self does not need to be run + if not self.has_been_run: + self.run_code() + else: + # On the contrary if run_left was called outside of run_right + # self is the block that was clicked + # so self needs to be run + self.run_code() + + def run_right(self): + """Run all of the output blocks and all their dependencies""" + # If the user presses right run when running, cancel the execution + if self.is_running: + self._interrupt_execution() + return + + # If no output, run left + if not self.has_output(): + return self.run_left(in_run_right=True) + + # Same as run_left but instead of running the blocks, we'll use run_left + graph = self.scene().create_graph() + edges = bfs_edges(graph, self) + blocks_to_run: List["OCBExecutableBlock"] = [ + self] + [v for _, v in edges] + for block in blocks_to_run[::-1]: + block.run_left(in_run_right=True) + + def reset_has_been_run(self): + """ Called when the output is an error """ + self.has_been_run = False + + + @property + def source(self) -> str: + """Source code""" + raise NotImplementedError("source(self) should be overriden") + + @source.setter + def source(self, value: str): + raise NotImplementedError("source(self) should be overriden") + + + def handle_stdout(self, value: str): + """Handle the stdout signal""" + pass + + def handle_image(self, image: str): + """Handle the image signal""" + pass + + def serialize(self): + """Return a serialized version of this block""" + base_dict = super().serialize() + base_dict["source"] = self.source + return base_dict + + def deserialize( + self, data: OrderedDict, hashmap: dict = None, restore_id: bool = True + ): + """Restore a codeblock from it's serialized state""" + for dataname in ("source"): + if dataname in data: + setattr(self, dataname, data[dataname]) + super().deserialize(data, hashmap, restore_id) diff --git a/opencodeblocks/blocks/sliderblock.py b/opencodeblocks/blocks/sliderblock.py index 21505b78..74833b96 100644 --- a/opencodeblocks/blocks/sliderblock.py +++ b/opencodeblocks/blocks/sliderblock.py @@ -8,7 +8,7 @@ from PyQt5.QtCore import Qt from PyQt5.QtWidgets import QHBoxLayout, QLabel, QLineEdit, QSlider, QVBoxLayout from opencodeblocks.blocks.block import OCBBlock - +from opencodeblocks.graphics.kernel import get_main_kernel class OCBSliderBlock(OCBBlock): """ @@ -47,10 +47,21 @@ def valueChanged(self): """ This is called when the value of the slider changes """ python_code = f"{self.var_name} = {self.value}" self.variable_value.setText(f"{self.value}") - - # The code execution part will be added when the execution flow is merged. - # We print for now - print(python_code) + + kernel = get_main_kernel() + kernel.execution_queue.append((self, python_code)) + if kernel.busy is False: + kernel.run_queue() + + + def reset_has_been_run(self): + pass + def reset_buttons(self): + pass + def handle_stdout(self): + pass + def handle_image(self): + pass @property def value(self): diff --git a/opencodeblocks/graphics/kernel.py b/opencodeblocks/graphics/kernel.py index c5a1f509..4adf9d7b 100644 --- a/opencodeblocks/graphics/kernel.py +++ b/opencodeblocks/graphics/kernel.py @@ -3,6 +3,7 @@ import queue from typing import Tuple +from PyQt5.QtCore import QThreadPool from jupyter_client.manager import start_new_kernel from opencodeblocks.graphics.worker import Worker @@ -68,9 +69,9 @@ def run_block(self, block, code: str): worker.signals.stdout.connect(block.handle_stdout) worker.signals.image.connect(block.handle_image) worker.signals.finished.connect(self.run_queue) - worker.signals.finished_block.connect(block.reset_buttons) + worker.signals.finished.connect(block.execution_finished) worker.signals.error.connect(block.reset_has_been_run) - block.source_editor.threadpool.start(worker) + get_main_threadpool().start(worker) def run_queue(self): """ Runs the next code in the queue """ @@ -138,3 +139,13 @@ def __del__(self): Shuts down the kernel """ self.kernel_manager.shutdown_kernel() + + +kernel = Kernel() +threadpool = QThreadPool() + +def get_main_kernel(): + return kernel + +def get_main_threadpool(): + return threadpool \ No newline at end of file diff --git a/opencodeblocks/graphics/pyeditor.py b/opencodeblocks/graphics/pyeditor.py index e7877257..766e5be9 100644 --- a/opencodeblocks/graphics/pyeditor.py +++ b/opencodeblocks/graphics/pyeditor.py @@ -4,16 +4,13 @@ """ Module for OCB in block python editor. """ from typing import TYPE_CHECKING, List -from PyQt5.QtCore import QThreadPool, Qt +from PyQt5.QtCore import Qt from PyQt5.QtGui import QFocusEvent, QFont, QFontMetrics, QColor, QMouseEvent, QWheelEvent from PyQt5.Qsci import QsciScintilla, QsciLexerPython from opencodeblocks.graphics.theme_manager import theme_manager from opencodeblocks.blocks.block import OCBBlock -from opencodeblocks.graphics.kernel import Kernel - -kernel = Kernel() -threadpool = QThreadPool() +from opencodeblocks.graphics.kernel import get_main_kernel, get_main_threadpool if TYPE_CHECKING: from opencodeblocks.graphics.view import OCBView @@ -33,8 +30,8 @@ def __init__(self, block: OCBBlock): super().__init__(None) self._mode = "NOOP" self.block = block - self.kernel = kernel - self.threadpool = threadpool + self.kernel = get_main_kernel() + self.threadpool = get_main_threadpool() self.update_theme() theme_manager().themeChanged.connect(self.update_theme) diff --git a/opencodeblocks/graphics/worker.py b/opencodeblocks/graphics/worker.py index abb6185a..126df9d3 100644 --- a/opencodeblocks/graphics/worker.py +++ b/opencodeblocks/graphics/worker.py @@ -44,7 +44,6 @@ async def run_code(self): elif output_type == 'error': self.signals.error.emit() self.signals.finished.emit() - self.signals.finished_block.emit() def run(self): """ Execute the run_code method asynchronously. """ From 25944e913a0abfa426f0cb654c3a026f9b5bc580 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Del=C3=A8gue?= Date: Sat, 11 Dec 2021 21:54:14 +0100 Subject: [PATCH 02/22] =?UTF-8?q?=E2=98=94=20Correction=20for=20test=20and?= =?UTF-8?q?=20fix=20for=20#112?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- opencodeblocks/blocks/codeblock.py | 5 ++++- opencodeblocks/blocks/executableblock.py | 23 ++++++++++------------- tests/integration/blocks/test_block.py | 2 +- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/opencodeblocks/blocks/codeblock.py b/opencodeblocks/blocks/codeblock.py index 01824e3c..b96c275c 100644 --- a/opencodeblocks/blocks/codeblock.py +++ b/opencodeblocks/blocks/codeblock.py @@ -28,16 +28,19 @@ class OCBCodeBlock(OCBExecutableBlock): """ - def __init__(self, **kwargs): + def __init__(self, source: str = "", **kwargs): """ Create a new OCBCodeBlock. Initialize all the child widgets specific to this block type """ super().__init__(**kwargs) self.source_editor = PythonEditor(self) + self._source = "" self._stdout = "" + self.source = source + self.output_panel_height = self.height / 3 self._min_output_panel_height = 20 self._min_source_editor_height = 20 diff --git a/opencodeblocks/blocks/executableblock.py b/opencodeblocks/blocks/executableblock.py index 4420abad..b40a5f75 100644 --- a/opencodeblocks/blocks/executableblock.py +++ b/opencodeblocks/blocks/executableblock.py @@ -117,19 +117,16 @@ def run_left(self, in_run_right=False): self._interrupt_execution() return - # If no dependencies - if not self.has_input(): - return self.run_code() - - # Create the graph from the scene - graph = self.scene().create_graph() - # BFS through the input graph - edges = bfs_edges(graph, self, reverse=True) - # Run the blocks found except self - blocks_to_run: List["OCBExecutableblock"] = [v for _, v in edges] - for block in blocks_to_run[::-1]: - if not block.has_been_run: - block.run_code() + if self.has_input(): + # Create the graph from the scene + graph = self.scene().create_graph() + # BFS through the input graph + edges = bfs_edges(graph, self, reverse=True) + # Run the blocks found except self + blocks_to_run: List["OCBExecutableblock"] = [v for _, v in edges] + for block in blocks_to_run[::-1]: + if not block.has_been_run: + block.run_code() if in_run_right: # If run_left was called inside of run_right diff --git a/tests/integration/blocks/test_block.py b/tests/integration/blocks/test_block.py index 02e6253e..c14b34d1 100644 --- a/tests/integration/blocks/test_block.py +++ b/tests/integration/blocks/test_block.py @@ -11,7 +11,7 @@ from PyQt5.QtCore import QPointF -from opencodeblocks.blocks.codeblock import OCBBlock +from opencodeblocks.blocks.block import OCBBlock from opencodeblocks.graphics.window import OCBWindow from opencodeblocks.graphics.widget import OCBWidget From a9386ad212418fa3fe66f3b926d239549f7dde6d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Del=C3=A8gue?= Date: Sat, 11 Dec 2021 22:17:22 +0100 Subject: [PATCH 03/22] =?UTF-8?q?=F0=9F=8E=89=20Slider=20are=20executable?= =?UTF-8?q?=20and=20allow=20for=20real=20time=20displays=20!=20You=20can?= =?UTF-8?q?=20for=20example=20plot=20matplotlib=20graph=20and=20see=20chan?= =?UTF-8?q?ges=20in=20live=20!?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- opencodeblocks/blocks/codeblock.py | 18 ++++++++++++-- opencodeblocks/blocks/executableblock.py | 31 ++++++------------------ opencodeblocks/blocks/sliderblock.py | 30 +++++++++-------------- 3 files changed, 36 insertions(+), 43 deletions(-) diff --git a/opencodeblocks/blocks/codeblock.py b/opencodeblocks/blocks/codeblock.py index b96c275c..3a984b51 100644 --- a/opencodeblocks/blocks/codeblock.py +++ b/opencodeblocks/blocks/codeblock.py @@ -77,7 +77,7 @@ def init_run_button(self): run_button.move(int(self.edge_size), int(self.edge_size / 2)) run_button.setFixedSize(int(3 * self.edge_size), int(3 * self.edge_size)) - run_button.clicked.connect(self.run_left) + run_button.clicked.connect(self.handle_run_left) return run_button def init_run_all_button(self): @@ -85,11 +85,25 @@ def init_run_all_button(self): run_all_button = QPushButton(">>", self.root) run_all_button.setFixedSize( int(3 * self.edge_size), int(3 * self.edge_size)) - run_all_button.clicked.connect(self.run_right) + run_all_button.clicked.connect(self.handle_run_right) run_all_button.raise_() return run_all_button + def handle_run_right(self): + """ Called when the button for "Run All" was pressed""" + if self.is_running: + self._interrupt_execution() + else: + self.run_right() + + def handle_run_left(self): + """ Called when the button for "Run Left" was pressed""" + if self.is_running: + self._interrupt_execution() + else: + self.run_left() + def run_code(self): """Run the code in the block""" # Reset stdout diff --git a/opencodeblocks/blocks/executableblock.py b/opencodeblocks/blocks/executableblock.py index b40a5f75..110226d2 100644 --- a/opencodeblocks/blocks/executableblock.py +++ b/opencodeblocks/blocks/executableblock.py @@ -82,7 +82,8 @@ def execution_finished(self): """ self.is_running = False - def _interrupt_execution(self): + @staticmethod + def _interrupt_execution(): """ Interrupt an execution, reset the blocks in the queue """ kernel = get_main_kernel() for block, _ in kernel.execution_queue: @@ -112,11 +113,7 @@ def run_left(self, in_run_right=False): """ Run all of the block's dependencies and then run the block """ - # If the block was already running, cancel the execution - if self.is_running and not in_run_right: - self._interrupt_execution() - return - + if self.has_input(): # Create the graph from the scene graph = self.scene().create_graph() @@ -127,29 +124,17 @@ def run_left(self, in_run_right=False): for block in blocks_to_run[::-1]: if not block.has_been_run: block.run_code() - - if in_run_right: - # If run_left was called inside of run_right - # self is not necessarily the block that was clicked - # which means that self does not need to be run - if not self.has_been_run: - self.run_code() - else: - # On the contrary if run_left was called outside of run_right - # self is the block that was clicked - # so self needs to be run - self.run_code() + + if self.is_running: return + self.run_code() def run_right(self): """Run all of the output blocks and all their dependencies""" - # If the user presses right run when running, cancel the execution - if self.is_running: - self._interrupt_execution() - return # If no output, run left if not self.has_output(): - return self.run_left(in_run_right=True) + self.run_left(in_run_right=True) + return # Same as run_left but instead of running the blocks, we'll use run_left graph = self.scene().create_graph() diff --git a/opencodeblocks/blocks/sliderblock.py b/opencodeblocks/blocks/sliderblock.py index 74833b96..89953728 100644 --- a/opencodeblocks/blocks/sliderblock.py +++ b/opencodeblocks/blocks/sliderblock.py @@ -7,10 +7,10 @@ from typing import OrderedDict from PyQt5.QtCore import Qt from PyQt5.QtWidgets import QHBoxLayout, QLabel, QLineEdit, QSlider, QVBoxLayout -from opencodeblocks.blocks.block import OCBBlock +from opencodeblocks.blocks.executableblock import OCBExecutableBlock from opencodeblocks.graphics.kernel import get_main_kernel -class OCBSliderBlock(OCBBlock): +class OCBSliderBlock(OCBExecutableBlock): """ Features a slider ranging from 0 to 1 and an area to choose what value to assign the slider to. """ @@ -45,23 +45,17 @@ def __init__(self, **kwargs): def valueChanged(self): """ This is called when the value of the slider changes """ - python_code = f"{self.var_name} = {self.value}" self.variable_value.setText(f"{self.value}") - - kernel = get_main_kernel() - kernel.execution_queue.append((self, python_code)) - if kernel.busy is False: - kernel.run_queue() - - - def reset_has_been_run(self): - pass - def reset_buttons(self): - pass - def handle_stdout(self): - pass - def handle_image(self): - pass + self.run_right() + + @property + def source(self): + """ Get the source code of the slider """ + python_code = f"{self.var_name} = {self.value}" + return python_code + @source.setter + def source(self, value: str): + raise RuntimeError("The source of a sliderblock is read-only.") @property def value(self): From e90d221f1b5cbd12f1184134822be613563cb001 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Del=C3=A8gue?= Date: Sat, 11 Dec 2021 23:10:09 +0100 Subject: [PATCH 04/22] =?UTF-8?q?=F0=9F=93=9D=20Add=20an=20example=20for?= =?UTF-8?q?=20sliders=20with=20a=20linear=20classifier.=20=E2=9C=A8=20Remo?= =?UTF-8?q?ve=20unused=20imports?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- examples/linear_classifier.ipyg | 609 +++++++++++++++++++++++ opencodeblocks/blocks/codeblock.py | 3 +- opencodeblocks/blocks/executableblock.py | 8 +- opencodeblocks/blocks/sliderblock.py | 3 +- opencodeblocks/graphics/worker.py | 1 + 5 files changed, 614 insertions(+), 10 deletions(-) create mode 100644 examples/linear_classifier.ipyg diff --git a/examples/linear_classifier.ipyg b/examples/linear_classifier.ipyg new file mode 100644 index 00000000..3aa765eb --- /dev/null +++ b/examples/linear_classifier.ipyg @@ -0,0 +1,609 @@ +{ + "id": 2034509196736, + "blocks": [ + { + "id": 2034509423808, + "title": "Imports", + "block_type": "OCBCodeBlock", + "splitter_pos": [ + 227, + 0 + ], + "position": [ + -837.0, + -95.0 + ], + "width": 501, + "height": 286, + "metadata": { + "title_metadata": { + "color": "white", + "font": "Ubuntu", + "size": 10 + } + }, + "sockets": [ + { + "id": 2034509424672, + "type": "input", + "position": [ + 0.0, + 53.0 + ], + "metadata": { + "color": "#FF55FFF0", + "linecolor": "#FF000000", + "linewidth": 1.0, + "radius": 10.0 + } + }, + { + "id": 2034509424816, + "type": "output", + "position": [ + 501.0, + 53.0 + ], + "metadata": { + "color": "#FF55FFF0", + "linecolor": "#FF000000", + "linewidth": 1.0, + "radius": 10.0 + } + } + ], + "source": "from matplotlib import pyplot as plt\r\nfrom sklearn import linear_model\r\nfrom random import seed, random", + "stdout": "" + }, + { + "id": 2034638878464, + "title": "", + "block_type": "OCBMarkdownBlock", + "splitter_pos": [ + 0, + 194 + ], + "position": [ + -940.0, + -467.0 + ], + "width": 677, + "height": 253, + "metadata": { + "title_metadata": { + "color": "white", + "font": "Ubuntu", + "size": 10 + } + }, + "sockets": [], + "text": "# Linear regressions\r\n\r\nThe example showcases how a graph notebook can be used to teach\r\nhow linear regressions work." + }, + { + "id": 2034686482320, + "title": "Show the data", + "block_type": "OCBCodeBlock", + "splitter_pos": [ + 0, + 403 + ], + "position": [ + 757.0, + 106.0 + ], + "width": 663, + "height": 462, + "metadata": { + "title_metadata": { + "color": "white", + "font": "Ubuntu", + "size": 10 + } + }, + "sockets": [ + { + "id": 2034686483184, + "type": "input", + "position": [ + 0.0, + 53.0 + ], + "metadata": { + "color": "#FF55FFF0", + "linecolor": "#FF000000", + "linewidth": 1.0, + "radius": 10.0 + } + }, + { + "id": 2034686483328, + "type": "output", + "position": [ + 663.0, + 53.0 + ], + "metadata": { + "color": "#FF55FFF0", + "linecolor": "#FF000000", + "linewidth": 1.0, + "radius": 10.0 + } + } + ], + "source": "um = 10 * m\r\nub = 2 * b\r\nuy = [um*i+ub for i in x]\r\n\r\nplt.plot(x,uy)\r\nplt.scatter(x,y)", + "stdout": "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\n" + }, + { + "id": 2034723533728, + "title": "Generate some data to plot", + "block_type": "OCBCodeBlock", + "splitter_pos": [ + 275, + 0 + ], + "position": [ + -211.0, + -101.0 + ], + "width": 641, + "height": 334, + "metadata": { + "title_metadata": { + "color": "white", + "font": "Ubuntu", + "size": 10 + } + }, + "sockets": [ + { + "id": 2034723534592, + "type": "input", + "position": [ + 0.0, + 53.0 + ], + "metadata": { + "color": "#FF55FFF0", + "linecolor": "#FF000000", + "linewidth": 1.0, + "radius": 10.0 + } + }, + { + "id": 2034723534736, + "type": "output", + "position": [ + 641.0, + 53.0 + ], + "metadata": { + "color": "#FF55FFF0", + "linecolor": "#FF000000", + "linewidth": 1.0, + "radius": 10.0 + } + } + ], + "source": "seed(132)\r\nx = [3 + random() * 10 for i in range(40)]\r\ny = [4.15 * i + random() * 2 for i in x]", + "stdout": "" + }, + { + "id": 2034723677808, + "title": "Slider", + "block_type": "OCBSliderBlock", + "splitter_pos": [], + "position": [ + -175.0, + 296.0 + ], + "width": 618, + "height": 184, + "metadata": { + "title_metadata": { + "color": "white", + "font": "Ubuntu", + "size": 10 + } + }, + "sockets": [ + { + "id": 2034723678672, + "type": "input", + "position": [ + 0.0, + 50.0 + ], + "metadata": { + "color": "#FF55FFF0", + "linecolor": "#FF000000", + "linewidth": 1.0, + "radius": 10.0 + } + }, + { + "id": 2034723678816, + "type": "output", + "position": [ + 618.0, + 50.0 + ], + "metadata": { + "color": "#FF55FFF0", + "linecolor": "#FF000000", + "linewidth": 1.0, + "radius": 10.0 + } + } + ], + "source": "b = 0.12", + "value": "0.12", + "var_name": "b" + }, + { + "id": 2034723714816, + "title": "Slider", + "block_type": "OCBSliderBlock", + "splitter_pos": [], + "position": [ + -184.0, + 550.0 + ], + "width": 618, + "height": 184, + "metadata": { + "title_metadata": { + "color": "white", + "font": "Ubuntu", + "size": 10 + } + }, + "sockets": [ + { + "id": 2034723715680, + "type": "input", + "position": [ + 0.0, + 50.0 + ], + "metadata": { + "color": "#FF55FFF0", + "linecolor": "#FF000000", + "linewidth": 1.0, + "radius": 10.0 + } + }, + { + "id": 2034723715824, + "type": "output", + "position": [ + 618.0, + 50.0 + ], + "metadata": { + "color": "#FF55FFF0", + "linecolor": "#FF000000", + "linewidth": 1.0, + "radius": 10.0 + } + } + ], + "source": "m = 0.12", + "value": "0.12", + "var_name": "m" + }, + { + "id": 2034879162976, + "title": "Regression", + "block_type": "OCBCodeBlock", + "splitter_pos": [ + 0, + 349 + ], + "position": [ + 784.0, + -676.0 + ], + "width": 615, + "height": 408, + "metadata": { + "title_metadata": { + "color": "white", + "font": "Ubuntu", + "size": 10 + } + }, + "sockets": [ + { + "id": 2034879163840, + "type": "input", + "position": [ + 0.0, + 53.0 + ], + "metadata": { + "color": "#FF55FFF0", + "linecolor": "#FF000000", + "linewidth": 1.0, + "radius": 10.0 + } + }, + { + "id": 2034879163984, + "type": "output", + "position": [ + 615.0, + 53.0 + ], + "metadata": { + "color": "#FF55FFF0", + "linecolor": "#FF000000", + "linewidth": 1.0, + "radius": 10.0 + } + } + ], + "source": "my = reg.predict([[i] for i in x])\r\nprint(my)\r\nplt.plot(x,my)\r\nplt.scatter(x,y)", + "stdout": "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\n" + }, + { + "id": 2034879286288, + "title": "Show user input", + "block_type": "OCBCodeBlock", + "splitter_pos": [ + 0, + 214 + ], + "position": [ + 757.0, + 619.0 + ], + "width": 685, + "height": 273, + "metadata": { + "title_metadata": { + "color": "white", + "font": "Ubuntu", + "size": 10 + } + }, + "sockets": [ + { + "id": 2034879287152, + "type": "input", + "position": [ + 0.0, + 53.0 + ], + "metadata": { + "color": "#FF55FFF0", + "linecolor": "#FF000000", + "linewidth": 1.0, + "radius": 10.0 + } + }, + { + "id": 2034879197248, + "type": "output", + "position": [ + 685.0, + 53.0 + ], + "metadata": { + "color": "#FF55FFF0", + "linecolor": "#FF000000", + "linewidth": 1.0, + "radius": 10.0 + } + } + ], + "source": "print(\"Your manual regression: \")\r\nprint(f\"y = x * {um} + {ub}\")\r\n\r\nprint(\"Accuracy:\")\r\nu = sum([(y[i] - (x[i]*um+ub)) ** 2 for i in range(len(x))])\r\ny_mean = sum(y) / len(y)\r\nv = sum([(y_i - y_mean) ** 2 for y_i in y])\r\nprint(1 - u/v)\r\n", + "stdout": "Your manual regression: \ny = x * 0.8999999999999999 + 0.24\nAccuracy:\n-6.737344734867353\n" + }, + { + "id": 2034886210608, + "title": "Create a new linear model", + "block_type": "OCBCodeBlock", + "splitter_pos": [ + 335, + 0 + ], + "position": [ + 10.0, + -562.0 + ], + "width": 656, + "height": 394, + "metadata": { + "title_metadata": { + "color": "white", + "font": "Ubuntu", + "size": 10 + } + }, + "sockets": [ + { + "id": 2034886211472, + "type": "input", + "position": [ + 0.0, + 53.0 + ], + "metadata": { + "color": "#FF55FFF0", + "linecolor": "#FF000000", + "linewidth": 1.0, + "radius": 10.0 + } + }, + { + "id": 2034886211616, + "type": "output", + "position": [ + 656.0, + 53.0 + ], + "metadata": { + "color": "#FF55FFF0", + "linecolor": "#FF000000", + "linewidth": 1.0, + "radius": 10.0 + } + } + ], + "source": "reg = linear_model.LinearRegression()\r\nreg.fit([[i] for i in x],y)", + "stdout": "LinearRegression()" + }, + { + "id": 2136886539168, + "title": "Show user input", + "block_type": "OCBCodeBlock", + "splitter_pos": [ + 0, + 224 + ], + "position": [ + 800.0, + -219.0 + ], + "width": 670, + "height": 283, + "metadata": { + "title_metadata": { + "color": "white", + "font": "Ubuntu", + "size": 10 + } + }, + "sockets": [ + { + "id": 2136886540752, + "type": "input", + "position": [ + 0.0, + 53.0 + ], + "metadata": { + "color": "#FF55FFF0", + "linecolor": "#FF000000", + "linewidth": 1.0, + "radius": 10.0 + } + }, + { + "id": 2136886540896, + "type": "output", + "position": [ + 670.0, + 53.0 + ], + "metadata": { + "color": "#FF55FFF0", + "linecolor": "#FF000000", + "linewidth": 1.0, + "radius": 10.0 + } + } + ], + "source": "print(\"Automatic regression: \")\r\nprint(f\"y = x * {reg.coef_[0]} + {reg.predict([[0]])[0]}\")\r\n\r\nprint(\"Accuracy: (closer to 1 = better)\")\r\nprint(reg.score([[i] for i in x],y))", + "stdout": "Automatic regression: \ny = x * 4.171128552280276 + 0.8246691322815138\nAccuracy: (closer to 1 = better)\n0.9977264063505547\n" + } + ], + "edges": [ + { + "id": 2034686480592, + "path_type": "bezier", + "source": { + "block": 2034509423808, + "socket": 2034509424816 + }, + "destination": { + "block": 2034723533728, + "socket": 2034723534592 + } + }, + { + "id": 2034686599952, + "path_type": "bezier", + "source": { + "block": 2034723533728, + "socket": 2034723534736 + }, + "destination": { + "block": 2034686482320, + "socket": 2034686483184 + } + }, + { + "id": 2034879160672, + "path_type": "bezier", + "source": { + "block": 2034723714816, + "socket": 2034723715824 + }, + "destination": { + "block": 2034686482320, + "socket": 2034686483184 + } + }, + { + "id": 2034879161104, + "path_type": "bezier", + "source": { + "block": 2034723677808, + "socket": 2034723678816 + }, + "destination": { + "block": 2034686482320, + "socket": 2034686483184 + } + }, + { + "id": 2034882738640, + "path_type": "bezier", + "source": { + "block": 2034723533728, + "socket": 2034723534736 + }, + "destination": { + "block": 2034886210608, + "socket": 2034886211472 + } + }, + { + "id": 2034882739360, + "path_type": "bezier", + "source": { + "block": 2034886210608, + "socket": 2034886211616 + }, + "destination": { + "block": 2034879162976, + "socket": 2034879163840 + } + }, + { + "id": 2034884170944, + "path_type": "bezier", + "source": { + "block": 2034686482320, + "socket": 2034686483328 + }, + "destination": { + "block": 2034879286288, + "socket": 2034879287152 + } + }, + { + "id": 2136887093136, + "path_type": "bezier", + "source": { + "block": 2034886210608, + "socket": 2034886211616 + }, + "destination": { + "block": 2136886539168, + "socket": 2136886540752 + } + } + ] +} \ No newline at end of file diff --git a/opencodeblocks/blocks/codeblock.py b/opencodeblocks/blocks/codeblock.py index 3a984b51..2cae8fae 100644 --- a/opencodeblocks/blocks/codeblock.py +++ b/opencodeblocks/blocks/codeblock.py @@ -3,11 +3,10 @@ """ Module for the base OCB Code Block. """ -from typing import List, OrderedDict +from typing import OrderedDict from PyQt5.QtWidgets import QPushButton, QTextEdit from ansi2html import Ansi2HTMLConverter -from networkx.algorithms.traversal.breadth_first_search import bfs_edges from opencodeblocks.blocks.executableblock import OCBExecutableBlock from opencodeblocks.graphics.socket import OCBSocket diff --git a/opencodeblocks/blocks/executableblock.py b/opencodeblocks/blocks/executableblock.py index 110226d2..b67f7535 100644 --- a/opencodeblocks/blocks/executableblock.py +++ b/opencodeblocks/blocks/executableblock.py @@ -2,7 +2,6 @@ from typing import List, OrderedDict -from ansi2html import Ansi2HTMLConverter from networkx.algorithms.traversal.breadth_first_search import bfs_edges from opencodeblocks.blocks.block import OCBBlock @@ -10,9 +9,6 @@ from opencodeblocks.graphics.kernel import get_main_kernel -conv = Ansi2HTMLConverter() - - class OCBExecutableBlock(OCBBlock): """ @@ -113,14 +109,14 @@ def run_left(self, in_run_right=False): """ Run all of the block's dependencies and then run the block """ - + if self.has_input(): # Create the graph from the scene graph = self.scene().create_graph() # BFS through the input graph edges = bfs_edges(graph, self, reverse=True) # Run the blocks found except self - blocks_to_run: List["OCBExecutableblock"] = [v for _, v in edges] + blocks_to_run: List["OCBExecutableBlock"] = [v for _, v in edges] for block in blocks_to_run[::-1]: if not block.has_been_run: block.run_code() diff --git a/opencodeblocks/blocks/sliderblock.py b/opencodeblocks/blocks/sliderblock.py index 89953728..ca17f17c 100644 --- a/opencodeblocks/blocks/sliderblock.py +++ b/opencodeblocks/blocks/sliderblock.py @@ -8,7 +8,6 @@ from PyQt5.QtCore import Qt from PyQt5.QtWidgets import QHBoxLayout, QLabel, QLineEdit, QSlider, QVBoxLayout from opencodeblocks.blocks.executableblock import OCBExecutableBlock -from opencodeblocks.graphics.kernel import get_main_kernel class OCBSliderBlock(OCBExecutableBlock): """ @@ -23,7 +22,7 @@ def __init__(self, **kwargs): self.slider = QSlider(Qt.Horizontal) self.slider.valueChanged.connect(self.valueChanged) - self.variable_layout = QHBoxLayout(self.root) + self.variable_layout = QHBoxLayout() self.variable_text = QLineEdit("slider_value") self.variable_value = QLabel(f"{self.slider.value()/100}") diff --git a/opencodeblocks/graphics/worker.py b/opencodeblocks/graphics/worker.py index 126df9d3..15323797 100644 --- a/opencodeblocks/graphics/worker.py +++ b/opencodeblocks/graphics/worker.py @@ -43,6 +43,7 @@ async def run_code(self): self.signals.image.emit(output) elif output_type == 'error': self.signals.error.emit() + self.signals.stdout.emit(output) self.signals.finished.emit() def run(self): From 27da4c176fbd1865c4728c9de82ee9ef314c3ca4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Del=C3=A8gue?= Date: Sat, 11 Dec 2021 23:25:37 +0100 Subject: [PATCH 05/22] =?UTF-8?q?=F0=9F=8E=89=20Make=20drawing=20block=20e?= =?UTF-8?q?xecutable?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- opencodeblocks/blocks/drawingblock.py | 39 ++++++++++++++++++++------- opencodeblocks/blocks/sliderblock.py | 2 +- 2 files changed, 30 insertions(+), 11 deletions(-) diff --git a/opencodeblocks/blocks/drawingblock.py b/opencodeblocks/blocks/drawingblock.py index f45cfc62..ba951c0f 100644 --- a/opencodeblocks/blocks/drawingblock.py +++ b/opencodeblocks/blocks/drawingblock.py @@ -4,18 +4,19 @@ import json from typing import OrderedDict -from PyQt5.QtCore import Qt +from PyQt5.QtCore import Qt, pyqtSignal from PyQt5.QtGui import QColor, QMouseEvent, QPaintEvent, QPainter from PyQt5.QtWidgets import QPushButton, QWidget -from opencodeblocks.blocks.block import OCBBlock +from opencodeblocks.blocks.executableblock import OCBExecutableBlock eps = 1 - class DrawableWidget(QWidget): """ A drawable widget is a canvas like widget on which you can doodle """ + on_value_changed = pyqtSignal() + def __init__(self, parent: QWidget): """ Create a new Drawable widget """ super().__init__(parent) @@ -27,14 +28,14 @@ def __init__(self, parent: QWidget): for _ in range(self.pixel_width): self.color_buffer.append([]) for _ in range(self.pixel_height): - # color hex encoded as AARRGGBB - self.color_buffer[-1].append(0xFFFFFFFF) + # 0 = white, 1 = black + self.color_buffer[-1].append(0) def clearDrawing(self): """ Clear the drawing """ for i in range(self.pixel_width): for j in range(self.pixel_height): - self.color_buffer[i][j] = 0xFFFFFFFF + self.color_buffer[i][j] = 0 def paintEvent(self, evt: QPaintEvent): """ Draw the content of the widget """ @@ -50,8 +51,9 @@ def paintEvent(self, evt: QPaintEvent): h * j, w + eps, h + eps, - QColor.fromRgb( - self.color_buffer[i][j])) + # hex color encoded as AARRGGBB + QColor.fromRgb(0xFF000000 if self.color_buffer[i][j] else 0xFFFFFFFF) + ) def mouseMoveEvent(self, evt: QMouseEvent): """ Change the drawing when dragging the mouse around""" @@ -59,8 +61,9 @@ def mouseMoveEvent(self, evt: QMouseEvent): x = floor(evt.x() / self.width() * self.pixel_width) y = floor(evt.y() / self.height() * self.pixel_height) if 0 <= x < self.pixel_width and 0 <= y < self.pixel_height: - self.color_buffer[x][y] = 0xFF000000 + self.color_buffer[x][y] = 1 self.repaint() + self.on_value_changed.emit() def mousePressEvent(self, evt: QMouseEvent): """ Signal that the drawing starts """ @@ -71,7 +74,7 @@ def mouseReleaseEvent(self, evt: QMouseEvent): self.mouse_down = False -class OCBDrawingBlock(OCBBlock): +class OCBDrawingBlock(OCBExecutableBlock): """ An OCBBlock on which you can draw, to test your CNNs for example""" def __init__(self, **kwargs): @@ -79,6 +82,8 @@ def __init__(self, **kwargs): super().__init__(**kwargs) self.draw_area = DrawableWidget(self.root) + self.draw_area.on_value_changed.connect(self.valueChanged) + self.var_name = "drawing" self.splitter.addWidget(self.draw_area) # QGraphicsView self.run_button = QPushButton("Clear", self.root) @@ -105,6 +110,20 @@ def serialize(self): return base_dict + def valueChanged(self): + """ Called when the content of the drawing block changes. """ + self.run_right() + + @property + def source(self): + """ The "source code" of the drawingblock i.e an assignement to the drawing buffer """ + python_code = f"{self.var_name} = {repr(self.draw_area.color_buffer)}" + return python_code + @source.setter + def source(self, value: str): + raise RuntimeError("The source of a drawingblock is read-only.") + + def deserialize(self, data: OrderedDict, hashmap: dict = None, restore_id: bool = True): """ Restore a markdown block from it's serialized state """ diff --git a/opencodeblocks/blocks/sliderblock.py b/opencodeblocks/blocks/sliderblock.py index ca17f17c..cd4c2f01 100644 --- a/opencodeblocks/blocks/sliderblock.py +++ b/opencodeblocks/blocks/sliderblock.py @@ -49,7 +49,7 @@ def valueChanged(self): @property def source(self): - """ Get the source code of the slider """ + """ The "source code" of the slider i.e an assignement to the value of the slider """ python_code = f"{self.var_name} = {self.value}" return python_code @source.setter From a19cb24f3013e522fb65efa9e66caf5c9ab574b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Del=C3=A8gue?= Date: Sat, 11 Dec 2021 23:32:19 +0100 Subject: [PATCH 06/22] =?UTF-8?q?=E2=9C=A8=20Black=20formatting?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- opencodeblocks/blocks/__init__.py | 5 --- opencodeblocks/blocks/codeblock.py | 14 +++---- opencodeblocks/blocks/drawingblock.py | 53 +++++++++++++----------- opencodeblocks/blocks/executableblock.py | 50 +++++++--------------- opencodeblocks/graphics/kernel.py | 2 + 5 files changed, 53 insertions(+), 71 deletions(-) diff --git a/opencodeblocks/blocks/__init__.py b/opencodeblocks/blocks/__init__.py index 93e87f76..01b4f741 100644 --- a/opencodeblocks/blocks/__init__.py +++ b/opencodeblocks/blocks/__init__.py @@ -3,11 +3,6 @@ """ Module for the OCB Blocks of different types. """ -# Abstract blocks -from opencodeblocks.blocks.block import OCBBlock -from opencodeblocks.blocks.executableblock import OCBExecutableBlock - -# Real blocks from opencodeblocks.blocks.sliderblock import OCBSliderBlock from opencodeblocks.blocks.codeblock import OCBCodeBlock from opencodeblocks.blocks.markdownblock import OCBMarkdownBlock diff --git a/opencodeblocks/blocks/codeblock.py b/opencodeblocks/blocks/codeblock.py index 2cae8fae..3a52a5f3 100644 --- a/opencodeblocks/blocks/codeblock.py +++ b/opencodeblocks/blocks/codeblock.py @@ -34,7 +34,7 @@ def __init__(self, source: str = "", **kwargs): """ super().__init__(**kwargs) self.source_editor = PythonEditor(self) - + self._source = "" self._stdout = "" @@ -74,30 +74,28 @@ def init_run_button(self): """Initialize the run button""" run_button = QPushButton(">", self.root) run_button.move(int(self.edge_size), int(self.edge_size / 2)) - run_button.setFixedSize(int(3 * self.edge_size), - int(3 * self.edge_size)) + run_button.setFixedSize(int(3 * self.edge_size), int(3 * self.edge_size)) run_button.clicked.connect(self.handle_run_left) return run_button def init_run_all_button(self): """Initialize the run all button""" run_all_button = QPushButton(">>", self.root) - run_all_button.setFixedSize( - int(3 * self.edge_size), int(3 * self.edge_size)) + run_all_button.setFixedSize(int(3 * self.edge_size), int(3 * self.edge_size)) run_all_button.clicked.connect(self.handle_run_right) run_all_button.raise_() return run_all_button def handle_run_right(self): - """ Called when the button for "Run All" was pressed""" + """Called when the button for "Run All" was pressed""" if self.is_running: self._interrupt_execution() else: self.run_right() def handle_run_left(self): - """ Called when the button for "Run Left" was pressed""" + """Called when the button for "Run Left" was pressed""" if self.is_running: self._interrupt_execution() else: @@ -112,7 +110,7 @@ def run_code(self): self.run_button.setText("...") self.run_all_button.setText("...") - super().run_code() # actually run the code + super().run_code() # actually run the code def execution_finished(self): """Reset the text of the run buttons""" diff --git a/opencodeblocks/blocks/drawingblock.py b/opencodeblocks/blocks/drawingblock.py index ba951c0f..98d1867f 100644 --- a/opencodeblocks/blocks/drawingblock.py +++ b/opencodeblocks/blocks/drawingblock.py @@ -12,13 +12,14 @@ eps = 1 + class DrawableWidget(QWidget): - """ A drawable widget is a canvas like widget on which you can doodle """ + """A drawable widget is a canvas like widget on which you can doodle""" on_value_changed = pyqtSignal() - + def __init__(self, parent: QWidget): - """ Create a new Drawable widget """ + """Create a new Drawable widget""" super().__init__(parent) self.setAttribute(Qt.WA_PaintOnScreen) self.pixel_width = 24 @@ -32,13 +33,13 @@ def __init__(self, parent: QWidget): self.color_buffer[-1].append(0) def clearDrawing(self): - """ Clear the drawing """ + """Clear the drawing""" for i in range(self.pixel_width): for j in range(self.pixel_height): self.color_buffer[i][j] = 0 def paintEvent(self, evt: QPaintEvent): - """ Draw the content of the widget """ + """Draw the content of the widget""" painter = QPainter(self) for i in range(self.pixel_width): @@ -52,11 +53,13 @@ def paintEvent(self, evt: QPaintEvent): w + eps, h + eps, # hex color encoded as AARRGGBB - QColor.fromRgb(0xFF000000 if self.color_buffer[i][j] else 0xFFFFFFFF) + QColor.fromRgb( + 0xFF000000 if self.color_buffer[i][j] else 0xFFFFFFFF + ), ) def mouseMoveEvent(self, evt: QMouseEvent): - """ Change the drawing when dragging the mouse around""" + """Change the drawing when dragging the mouse around""" if self.mouse_down: x = floor(evt.x() / self.width() * self.pixel_width) y = floor(evt.y() / self.height() * self.pixel_height) @@ -66,19 +69,19 @@ def mouseMoveEvent(self, evt: QMouseEvent): self.on_value_changed.emit() def mousePressEvent(self, evt: QMouseEvent): - """ Signal that the drawing starts """ + """Signal that the drawing starts""" self.mouse_down = True def mouseReleaseEvent(self, evt: QMouseEvent): - """ Signal that the drawing stops """ + """Signal that the drawing stops""" self.mouse_down = False class OCBDrawingBlock(OCBExecutableBlock): - """ An OCBBlock on which you can draw, to test your CNNs for example""" + """An OCBBlock on which you can draw, to test your CNNs for example""" def __init__(self, **kwargs): - """ Create a new OCBBlock""" + """Create a new OCBBlock""" super().__init__(**kwargs) self.draw_area = DrawableWidget(self.root) @@ -87,16 +90,17 @@ def __init__(self, **kwargs): self.splitter.addWidget(self.draw_area) # QGraphicsView self.run_button = QPushButton("Clear", self.root) - self.run_button.move(int(self.edge_size * 2), - int(self.title_widget.height() + self.edge_size * 2)) - self.run_button.setFixedSize( - int(8 * self.edge_size), int(3 * self.edge_size)) + self.run_button.move( + int(self.edge_size * 2), + int(self.title_widget.height() + self.edge_size * 2), + ) + self.run_button.setFixedSize(int(8 * self.edge_size), int(3 * self.edge_size)) self.run_button.clicked.connect(self.draw_area.clearDrawing) self.holder.setWidget(self.root) @property def drawing(self): - """ A json-encoded representation of the drawing """ + """A json-encoded representation of the drawing""" return json.dumps(self.draw_area.color_buffer) @drawing.setter @@ -104,30 +108,31 @@ def drawing(self, value: str): self.draw_area.color_buffer = json.loads(value) def serialize(self): - """ Return a serialized version of this widget """ + """Return a serialized version of this widget""" base_dict = super().serialize() base_dict["drawing"] = self.drawing return base_dict def valueChanged(self): - """ Called when the content of the drawing block changes. """ + """Called when the content of the drawing block changes.""" self.run_right() @property def source(self): - """ The "source code" of the drawingblock i.e an assignement to the drawing buffer """ + """The "source code" of the drawingblock i.e an assignement to the drawing buffer""" python_code = f"{self.var_name} = {repr(self.draw_area.color_buffer)}" return python_code + @source.setter def source(self, value: str): raise RuntimeError("The source of a drawingblock is read-only.") - - def deserialize(self, data: OrderedDict, - hashmap: dict = None, restore_id: bool = True): - """ Restore a markdown block from it's serialized state """ - for dataname in ['drawing']: + def deserialize( + self, data: OrderedDict, hashmap: dict = None, restore_id: bool = True + ): + """Restore a markdown block from it's serialized state""" + for dataname in ["drawing"]: if dataname in data: setattr(self, dataname, data[dataname]) diff --git a/opencodeblocks/blocks/executableblock.py b/opencodeblocks/blocks/executableblock.py index b67f7535..e194f351 100644 --- a/opencodeblocks/blocks/executableblock.py +++ b/opencodeblocks/blocks/executableblock.py @@ -9,6 +9,7 @@ from opencodeblocks.graphics.kernel import get_main_kernel + class OCBExecutableBlock(OCBBlock): """ @@ -24,11 +25,11 @@ class OCBExecutableBlock(OCBBlock): def __init__(self, **kwargs): """ - Create a new executable block. - Do not call this method except when inheriting from this class. + Create a new executable block. + Do not call this method except when inheriting from this class. """ super().__init__(**kwargs) - + self.has_been_run = False self.is_running = False @@ -58,13 +59,13 @@ def has_output(self) -> bool: return False def run_code(self): - """ Run the code in the block""" + """Run the code in the block""" # Queue the code to execute code = self.source kernel = get_main_kernel() kernel.execution_queue.append((self, code)) - + self.is_running = True if kernel.busy is False: @@ -80,7 +81,7 @@ def execution_finished(self): @staticmethod def _interrupt_execution(): - """ Interrupt an execution, reset the blocks in the queue """ + """Interrupt an execution, reset the blocks in the queue""" kernel = get_main_kernel() for block, _ in kernel.execution_queue: # Reset the blocks that have not been run @@ -91,21 +92,7 @@ def _interrupt_execution(): # Interrupt the kernel kernel.kernel_manager.interrupt_kernel() - def has_input(self) -> bool: - """Checks whether a block has connected input blocks""" - for input_socket in self.sockets_in: - if len(input_socket.edges) != 0: - return True - return False - - def has_output(self) -> bool: - """Checks whether a block has connected output blocks""" - for output_socket in self.sockets_out: - if len(output_socket.edges) != 0: - return True - return False - - def run_left(self, in_run_right=False): + def run_left(self): """ Run all of the block's dependencies and then run the block """ @@ -120,8 +107,9 @@ def run_left(self, in_run_right=False): for block in blocks_to_run[::-1]: if not block.has_been_run: block.run_code() - - if self.is_running: return + + if self.is_running: + return self.run_code() def run_right(self): @@ -129,22 +117,20 @@ def run_right(self): # If no output, run left if not self.has_output(): - self.run_left(in_run_right=True) + self.run_left() return # Same as run_left but instead of running the blocks, we'll use run_left graph = self.scene().create_graph() edges = bfs_edges(graph, self) - blocks_to_run: List["OCBExecutableBlock"] = [ - self] + [v for _, v in edges] + blocks_to_run: List["OCBExecutableBlock"] = [self] + [v for _, v in edges] for block in blocks_to_run[::-1]: block.run_left(in_run_right=True) def reset_has_been_run(self): - """ Called when the output is an error """ + """Called when the output is an error""" self.has_been_run = False - @property def source(self) -> str: """Source code""" @@ -154,14 +140,11 @@ def source(self) -> str: def source(self, value: str): raise NotImplementedError("source(self) should be overriden") - def handle_stdout(self, value: str): """Handle the stdout signal""" - pass def handle_image(self, image: str): """Handle the image signal""" - pass def serialize(self): """Return a serialized version of this block""" @@ -173,7 +156,6 @@ def deserialize( self, data: OrderedDict, hashmap: dict = None, restore_id: bool = True ): """Restore a codeblock from it's serialized state""" - for dataname in ("source"): - if dataname in data: - setattr(self, dataname, data[dataname]) + if "source" in data: + setattr(self, "source", data["source"]) super().deserialize(data, hashmap, restore_id) diff --git a/opencodeblocks/graphics/kernel.py b/opencodeblocks/graphics/kernel.py index 4adf9d7b..2fbf5c30 100644 --- a/opencodeblocks/graphics/kernel.py +++ b/opencodeblocks/graphics/kernel.py @@ -145,7 +145,9 @@ def __del__(self): threadpool = QThreadPool() def get_main_kernel(): + """ Return a handle to the main kernel """ return kernel def get_main_threadpool(): + """ Return a handle to the thread pool """ return threadpool \ No newline at end of file From e9c26150c04568f65d934a9c974c04b390e9f9a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Del=C3=A8gue?= Date: Sat, 11 Dec 2021 23:41:29 +0100 Subject: [PATCH 07/22] =?UTF-8?q?=F0=9F=AA=B2=20Make=20kernel=20an=20attri?= =?UTF-8?q?bute=20of=20scene=20so=20that=20when=20multiple=20files=20are?= =?UTF-8?q?=20opened,=20they=20do=20not=20share=20the=20same=20kernel?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- opencodeblocks/blocks/containerblock.py | 78 ++---------------------- opencodeblocks/blocks/executableblock.py | 6 +- opencodeblocks/graphics/kernel.py | 15 +---- opencodeblocks/graphics/pyeditor.py | 3 - opencodeblocks/graphics/view.py | 3 +- opencodeblocks/scene/scene.py | 6 +- 6 files changed, 14 insertions(+), 97 deletions(-) diff --git a/opencodeblocks/blocks/containerblock.py b/opencodeblocks/blocks/containerblock.py index 21505b78..93cf4317 100644 --- a/opencodeblocks/blocks/containerblock.py +++ b/opencodeblocks/blocks/containerblock.py @@ -1,86 +1,16 @@ -# OpenCodeBlock an open-source tool for modular visual programing in python - """ -Exports OCBSliderBlock. +Exports OCBContainerBlock. """ -from typing import OrderedDict -from PyQt5.QtCore import Qt -from PyQt5.QtWidgets import QHBoxLayout, QLabel, QLineEdit, QSlider, QVBoxLayout from opencodeblocks.blocks.block import OCBBlock -class OCBSliderBlock(OCBBlock): +class OCBContainerBlock(OCBBlock): """ - Features a slider ranging from 0 to 1 and an area to choose what value to assign the slider to. + A block that can contain other blocks. """ def __init__(self, **kwargs): super().__init__(**kwargs) - self.layout = QVBoxLayout(self.root) - - self.slider = QSlider(Qt.Horizontal) - self.slider.valueChanged.connect(self.valueChanged) - - self.variable_layout = QHBoxLayout(self.root) - self.variable_text = QLineEdit("slider_value") - self.variable_value = QLabel(f"{self.slider.value()/100}") - - self.variable_text.setFixedWidth(self.root.width() / 2) - - self.variable_layout.addWidget(self.variable_text) - self.variable_layout.addWidget(self.variable_value) - - self.layout.setContentsMargins( - self.edge_size * 2, - self.title_widget.height() + self.edge_size * 2, - self.edge_size * 2, - self.edge_size * 2 - ) - self.layout.addWidget(self.slider) - self.layout.addLayout(self.variable_layout) - - self.holder.setWidget(self.root) - - def valueChanged(self): - """ This is called when the value of the slider changes """ - python_code = f"{self.var_name} = {self.value}" - self.variable_value.setText(f"{self.value}") - - # The code execution part will be added when the execution flow is merged. - # We print for now - print(python_code) - - @property - def value(self): - """ The value of the slider """ - return str(self.slider.value() / 100) - @value.setter - def value(self, value: str): - self.slider.setValue(int(float(value) * 100)) - - @property - def var_name(self): - """ The name of the python variable associated with the slider """ - return self.variable_text.text() - @var_name.setter - def var_name(self, value: str): - self.variable_text.setText(value) - - def serialize(self): - """ Return a serialized version of this widget """ - base_dict = super().serialize() - base_dict["value"] = self.value - base_dict["var_name"] = self.var_name - - return base_dict - - def deserialize(self, data: OrderedDict, - hashmap: dict = None, restore_id: bool = True): - """ Restore a slider block from it's serialized state """ - for dataname in ['value','var_name']: - if dataname in data: - setattr(self, dataname, data[dataname]) - - super().deserialize(data, hashmap, restore_id) + # WIP \ No newline at end of file diff --git a/opencodeblocks/blocks/executableblock.py b/opencodeblocks/blocks/executableblock.py index e194f351..79a7325f 100644 --- a/opencodeblocks/blocks/executableblock.py +++ b/opencodeblocks/blocks/executableblock.py @@ -7,8 +7,6 @@ from opencodeblocks.blocks.block import OCBBlock from opencodeblocks.graphics.socket import OCBSocket -from opencodeblocks.graphics.kernel import get_main_kernel - class OCBExecutableBlock(OCBBlock): @@ -63,7 +61,7 @@ def run_code(self): # Queue the code to execute code = self.source - kernel = get_main_kernel() + kernel = self.scene().kernel kernel.execution_queue.append((self, code)) self.is_running = True @@ -82,7 +80,7 @@ def execution_finished(self): @staticmethod def _interrupt_execution(): """Interrupt an execution, reset the blocks in the queue""" - kernel = get_main_kernel() + kernel = self.scene().kernel for block, _ in kernel.execution_queue: # Reset the blocks that have not been run block.execution_finished() diff --git a/opencodeblocks/graphics/kernel.py b/opencodeblocks/graphics/kernel.py index 2fbf5c30..660bfc4d 100644 --- a/opencodeblocks/graphics/kernel.py +++ b/opencodeblocks/graphics/kernel.py @@ -3,7 +3,6 @@ import queue from typing import Tuple -from PyQt5.QtCore import QThreadPool from jupyter_client.manager import start_new_kernel from opencodeblocks.graphics.worker import Worker @@ -71,7 +70,7 @@ def run_block(self, block, code: str): worker.signals.finished.connect(self.run_queue) worker.signals.finished.connect(block.execution_finished) worker.signals.error.connect(block.reset_has_been_run) - get_main_threadpool().start(worker) + block.scene().threadpool.start(worker) def run_queue(self): """ Runs the next code in the queue """ @@ -139,15 +138,3 @@ def __del__(self): Shuts down the kernel """ self.kernel_manager.shutdown_kernel() - - -kernel = Kernel() -threadpool = QThreadPool() - -def get_main_kernel(): - """ Return a handle to the main kernel """ - return kernel - -def get_main_threadpool(): - """ Return a handle to the thread pool """ - return threadpool \ No newline at end of file diff --git a/opencodeblocks/graphics/pyeditor.py b/opencodeblocks/graphics/pyeditor.py index 766e5be9..26d27772 100644 --- a/opencodeblocks/graphics/pyeditor.py +++ b/opencodeblocks/graphics/pyeditor.py @@ -10,7 +10,6 @@ from opencodeblocks.graphics.theme_manager import theme_manager from opencodeblocks.blocks.block import OCBBlock -from opencodeblocks.graphics.kernel import get_main_kernel, get_main_threadpool if TYPE_CHECKING: from opencodeblocks.graphics.view import OCBView @@ -30,8 +29,6 @@ def __init__(self, block: OCBBlock): super().__init__(None) self._mode = "NOOP" self.block = block - self.kernel = get_main_kernel() - self.threadpool = get_main_threadpool() self.update_theme() theme_manager().themeChanged.connect(self.update_theme) diff --git a/opencodeblocks/graphics/view.py b/opencodeblocks/graphics/view.py index 30a927ac..485eacaa 100644 --- a/opencodeblocks/graphics/view.py +++ b/opencodeblocks/graphics/view.py @@ -16,7 +16,8 @@ from opencodeblocks.scene import OCBScene from opencodeblocks.graphics.socket import OCBSocket from opencodeblocks.graphics.edge import OCBEdge -from opencodeblocks.blocks import OCBBlock, OCBCodeBlock +from opencodeblocks.blocks.block import OCBBlock +from opencodeblocks.blocks import OCBCodeBlock EPS: float = 1e-10 # To check if blocks are of size 0 diff --git a/opencodeblocks/scene/scene.py b/opencodeblocks/scene/scene.py index 9069e309..3539be40 100644 --- a/opencodeblocks/scene/scene.py +++ b/opencodeblocks/scene/scene.py @@ -8,7 +8,7 @@ from types import FunctionType, ModuleType from typing import List, OrderedDict, Union -from PyQt5.QtCore import QLine, QRectF +from PyQt5.QtCore import QLine, QRectF, QThreadPool from PyQt5.QtGui import QColor, QPainter, QPen from PyQt5.QtWidgets import QGraphicsScene @@ -19,6 +19,7 @@ from opencodeblocks.graphics.edge import OCBEdge from opencodeblocks.scene.clipboard import SceneClipboard from opencodeblocks.scene.history import SceneHistory +from opencodeblocks.graphics.kernel import Kernel import networkx as nx @@ -52,6 +53,9 @@ def __init__(self, parent=None, self.history = SceneHistory(self) self.clipboard = SceneClipboard(self) + self.kernel = Kernel() + self.threadpool = QThreadPool() + @property def has_been_modified(self): """ True if the scene has been modified, False otherwise. """ From 48ac3e8bb9322c5196835dbc79f827393224853a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Del=C3=A8gue?= Date: Sun, 12 Dec 2021 00:00:07 +0100 Subject: [PATCH 08/22] =?UTF-8?q?=E2=9C=A8=20Fixes=20to=20pass=20the=20CI?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- opencodeblocks/blocks/__init__.py | 2 -- opencodeblocks/blocks/executableblock.py | 3 +-- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/opencodeblocks/blocks/__init__.py b/opencodeblocks/blocks/__init__.py index 01b4f741..22044feb 100644 --- a/opencodeblocks/blocks/__init__.py +++ b/opencodeblocks/blocks/__init__.py @@ -7,5 +7,3 @@ from opencodeblocks.blocks.codeblock import OCBCodeBlock from opencodeblocks.blocks.markdownblock import OCBMarkdownBlock from opencodeblocks.blocks.drawingblock import OCBDrawingBlock - - diff --git a/opencodeblocks/blocks/executableblock.py b/opencodeblocks/blocks/executableblock.py index 79a7325f..985629d5 100644 --- a/opencodeblocks/blocks/executableblock.py +++ b/opencodeblocks/blocks/executableblock.py @@ -77,8 +77,7 @@ def execution_finished(self): """ self.is_running = False - @staticmethod - def _interrupt_execution(): + def _interrupt_execution(self): """Interrupt an execution, reset the blocks in the queue""" kernel = self.scene().kernel for block, _ in kernel.execution_queue: From 17fef8a44557a5e913c681f206cad858737d1f6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Del=C3=A8gue?= Date: Sun, 12 Dec 2021 00:09:36 +0100 Subject: [PATCH 09/22] =?UTF-8?q?=E2=9C=A8=20Refactoring=20tests=20to=20pa?= =?UTF-8?q?ss=20CI=20...?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/integration/blocks/test_block.py | 9 ++------- tests/integration/blocks/test_codeblock.py | 21 ++++++--------------- tests/integration/utils.py | 7 +++++++ 3 files changed, 15 insertions(+), 22 deletions(-) diff --git a/tests/integration/blocks/test_block.py b/tests/integration/blocks/test_block.py index c14b34d1..b07cecc1 100644 --- a/tests/integration/blocks/test_block.py +++ b/tests/integration/blocks/test_block.py @@ -12,20 +12,15 @@ from PyQt5.QtCore import QPointF from opencodeblocks.blocks.block import OCBBlock -from opencodeblocks.graphics.window import OCBWindow -from opencodeblocks.graphics.widget import OCBWidget -from tests.integration.utils import apply_function_inapp, CheckingQueue +from tests.integration.utils import apply_function_inapp, CheckingQueue, start_app class TestBlocks: @pytest.fixture(autouse=True) def setup(self): """Setup reused variables.""" - self.window = OCBWindow() - self.ocb_widget = OCBWidget() - self.subwindow = self.window.mdiArea.addSubWindow(self.ocb_widget) - self.subwindow.show() + start_app(self) self.block = OCBBlock(title="Testing block") def test_create_blocks(self, qtbot: QtBot): diff --git a/tests/integration/blocks/test_codeblock.py b/tests/integration/blocks/test_codeblock.py index 7ab6b67b..47a8cd28 100644 --- a/tests/integration/blocks/test_codeblock.py +++ b/tests/integration/blocks/test_codeblock.py @@ -5,30 +5,25 @@ Integration tests for the OCBCodeBlocks. """ +import time import pyautogui import pytest -from pytestqt.qtbot import QtBot from PyQt5.QtCore import QPointF from opencodeblocks.blocks.codeblock import OCBCodeBlock -from opencodeblocks.graphics.window import OCBWindow -from opencodeblocks.graphics.widget import OCBWidget -from tests.integration.utils import apply_function_inapp, CheckingQueue +from tests.integration.utils import apply_function_inapp, CheckingQueue, start_app -class TestCodeBlocks: +class TestCodeBlocks(): @pytest.fixture(autouse=True) def setup(self): """ Setup reused variables. """ - self.window = OCBWindow() - self.ocb_widget = OCBWidget() - self.subwindow = self.window.mdiArea.addSubWindow(self.ocb_widget) - self.subwindow.show() + start_app(self) - def test_run_python(self, qtbot: QtBot): + def test_run_python(self): """ run source code when run button is pressed. """ # Add a block with the source to the window @@ -56,12 +51,8 @@ def testing_run(msgQueue: CheckingQueue): pyautogui.mouseDown(button="left") pyautogui.mouseUp(button="left") - # qtbot.mouseMove(test_block.run_button) - # qtbot.mousePress(test_block.run_button, - # Qt.MouseButton.LeftButton, delay=1) - # qtbot.mouseRelease(test_block.run_button, Qt.MouseButton.LeftButton) + time.sleep(0.5) - # When the execution becomes non-blocking for the UI, a refactor will be needed here. msgQueue.check_equal(test_block.stdout.strip(), expected_result) msgQueue.stop() diff --git a/tests/integration/utils.py b/tests/integration/utils.py index d5cde156..20ed92ea 100644 --- a/tests/integration/utils.py +++ b/tests/integration/utils.py @@ -13,6 +13,7 @@ from queue import Queue from qtpy.QtWidgets import QApplication import pytest_check as check +from opencodeblocks.graphics.widget import OCBWidget from opencodeblocks.graphics.window import OCBWindow @@ -33,6 +34,12 @@ def stop(self): self.put([STOP_MSG]) +def start_app(obj): + obj.window = OCBWindow() + obj.ocb_widget = OCBWidget() + obj.subwindow = obj.window.mdiArea.addSubWindow(obj.ocb_widget) + obj.subwindow.show() + def apply_function_inapp(window: OCBWindow, run_func: Callable): if os.name == "nt": # If on windows From 2e4b40227ec3a5e67a57548f9747a3e3ea15dcf7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Del=C3=A8gue?= Date: Sun, 12 Dec 2021 00:39:14 +0100 Subject: [PATCH 10/22] =?UTF-8?q?=F0=9F=AA=B2=20Fix=20for=20#94=20as=20wel?= =?UTF-8?q?l=20as=20*ExecutingBlock*=20serialization=20issues.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- opencodeblocks/blocks/codeblock.py | 2 +- opencodeblocks/blocks/drawingblock.py | 4 +++- opencodeblocks/blocks/executableblock.py | 8 ++------ opencodeblocks/blocks/sliderblock.py | 7 +++++-- opencodeblocks/scene/scene.py | 6 ++++++ 5 files changed, 17 insertions(+), 10 deletions(-) diff --git a/opencodeblocks/blocks/codeblock.py b/opencodeblocks/blocks/codeblock.py index 3a52a5f3..5c43f4ef 100644 --- a/opencodeblocks/blocks/codeblock.py +++ b/opencodeblocks/blocks/codeblock.py @@ -226,7 +226,7 @@ def serialize(self): base_dict = super().serialize() base_dict["source"] = self.source base_dict["stdout"] = self.stdout - + return base_dict def deserialize( diff --git a/opencodeblocks/blocks/drawingblock.py b/opencodeblocks/blocks/drawingblock.py index 98d1867f..5d36e8f7 100644 --- a/opencodeblocks/blocks/drawingblock.py +++ b/opencodeblocks/blocks/drawingblock.py @@ -116,7 +116,9 @@ def serialize(self): def valueChanged(self): """Called when the content of the drawing block changes.""" - self.run_right() + # Make sure that the slider is initialized before trying to run it. + if self.scene() is not None: + self.run_right() @property def source(self): diff --git a/opencodeblocks/blocks/executableblock.py b/opencodeblocks/blocks/executableblock.py index 985629d5..63a82a58 100644 --- a/opencodeblocks/blocks/executableblock.py +++ b/opencodeblocks/blocks/executableblock.py @@ -122,7 +122,7 @@ def run_right(self): edges = bfs_edges(graph, self) blocks_to_run: List["OCBExecutableBlock"] = [self] + [v for _, v in edges] for block in blocks_to_run[::-1]: - block.run_left(in_run_right=True) + block.run_left() def reset_has_been_run(self): """Called when the output is an error""" @@ -145,14 +145,10 @@ def handle_image(self, image: str): def serialize(self): """Return a serialized version of this block""" - base_dict = super().serialize() - base_dict["source"] = self.source - return base_dict + return super().serialize() def deserialize( self, data: OrderedDict, hashmap: dict = None, restore_id: bool = True ): """Restore a codeblock from it's serialized state""" - if "source" in data: - setattr(self, "source", data["source"]) super().deserialize(data, hashmap, restore_id) diff --git a/opencodeblocks/blocks/sliderblock.py b/opencodeblocks/blocks/sliderblock.py index cd4c2f01..4d906e98 100644 --- a/opencodeblocks/blocks/sliderblock.py +++ b/opencodeblocks/blocks/sliderblock.py @@ -20,7 +20,6 @@ def __init__(self, **kwargs): self.layout = QVBoxLayout(self.root) self.slider = QSlider(Qt.Horizontal) - self.slider.valueChanged.connect(self.valueChanged) self.variable_layout = QHBoxLayout() self.variable_text = QLineEdit("slider_value") @@ -40,12 +39,16 @@ def __init__(self, **kwargs): self.layout.addWidget(self.slider) self.layout.addLayout(self.variable_layout) + self.slider.valueChanged.connect(self.valueChanged) + self.holder.setWidget(self.root) def valueChanged(self): """ This is called when the value of the slider changes """ self.variable_value.setText(f"{self.value}") - self.run_right() + # Make sure that the slider is initialized before trying to run it. + if self.scene() is not None: + self.run_right() @property def source(self): diff --git a/opencodeblocks/scene/scene.py b/opencodeblocks/scene/scene.py index 3539be40..019b5a1a 100644 --- a/opencodeblocks/scene/scene.py +++ b/opencodeblocks/scene/scene.py @@ -5,6 +5,7 @@ import math import json +from os import path from types import FunctionType, ModuleType from typing import List, OrderedDict, Union @@ -154,6 +155,11 @@ def load(self, filepath: str): self.history.checkpoint("Loaded scene") self.has_been_modified = False + # Add filepath to kernel path + dir_path = repr(path.abspath(path.dirname(filepath))) + setup_path_code = f"__import__(\"sys\").path[0] = {dir_path}" + self.kernel.execute(setup_path_code) + def load_from_ipyg(self, filepath: str): """ Load an interactive python graph (.ipyg) into the scene. From 58df0ce278415809a4e8abd4ba8222ba70cce496 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Del=C3=A8gue?= Date: Sun, 12 Dec 2021 00:55:28 +0100 Subject: [PATCH 11/22] =?UTF-8?q?=F0=9F=8E=89=20Work=20on=20implementing?= =?UTF-8?q?=20the=20container.=20A=20circular=20dependency=20needs=20to=20?= =?UTF-8?q?be=20removed.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- blocks/container.ocbb | 16 ++++++++++++++++ opencodeblocks/blocks/__init__.py | 1 + opencodeblocks/blocks/containerblock.py | 13 ++++++++++++- opencodeblocks/graphics/view.py | 2 +- opencodeblocks/scene/scene.py | 3 +-- 5 files changed, 31 insertions(+), 4 deletions(-) create mode 100644 blocks/container.ocbb diff --git a/blocks/container.ocbb b/blocks/container.ocbb new file mode 100644 index 00000000..e543d0b6 --- /dev/null +++ b/blocks/container.ocbb @@ -0,0 +1,16 @@ +{ + "title": "Container", + "block_type": "OCBContainerBlock", + "source": "", + "splitter_pos": [88,41], + "width": 618, + "height": 184, + "metadata": { + "title_metadata": { + "color": "white", + "font": "Ubuntu", + "size": 10, + "padding": 4.0 + } + } +} \ No newline at end of file diff --git a/opencodeblocks/blocks/__init__.py b/opencodeblocks/blocks/__init__.py index 22044feb..3a2eca69 100644 --- a/opencodeblocks/blocks/__init__.py +++ b/opencodeblocks/blocks/__init__.py @@ -7,3 +7,4 @@ from opencodeblocks.blocks.codeblock import OCBCodeBlock from opencodeblocks.blocks.markdownblock import OCBMarkdownBlock from opencodeblocks.blocks.drawingblock import OCBDrawingBlock +from opencodeblocks.blocks.containerblock import OCBContainerBlock diff --git a/opencodeblocks/blocks/containerblock.py b/opencodeblocks/blocks/containerblock.py index 93cf4317..c9026243 100644 --- a/opencodeblocks/blocks/containerblock.py +++ b/opencodeblocks/blocks/containerblock.py @@ -2,7 +2,11 @@ Exports OCBContainerBlock. """ +from PyQt5.QtWidgets import QVBoxLayout from opencodeblocks.blocks.block import OCBBlock +from opencodeblocks.graphics.view import OCBView +from opencodeblocks.graphics.widget import OCBWidget +from opencodeblocks.scene.scene import OCBScene class OCBContainerBlock(OCBBlock): @@ -13,4 +17,11 @@ class OCBContainerBlock(OCBBlock): def __init__(self, **kwargs): super().__init__(**kwargs) - # WIP \ No newline at end of file + self.layout = QVBoxLayout(self.root) + + self.scene = OCBScene() + self.scene.addHasBeenModifiedListener(self.updateTitle) + self.view = OCBView(self.scene) + self.layout.addWidget(self.view) + + self.holder.setWidget(self.root) diff --git a/opencodeblocks/graphics/view.py b/opencodeblocks/graphics/view.py index 485eacaa..9ace412f 100644 --- a/opencodeblocks/graphics/view.py +++ b/opencodeblocks/graphics/view.py @@ -17,7 +17,7 @@ from opencodeblocks.graphics.socket import OCBSocket from opencodeblocks.graphics.edge import OCBEdge from opencodeblocks.blocks.block import OCBBlock -from opencodeblocks.blocks import OCBCodeBlock +from opencodeblocks.blocks.codeblock import OCBCodeBlock EPS: float = 1e-10 # To check if blocks are of size 0 diff --git a/opencodeblocks/scene/scene.py b/opencodeblocks/scene/scene.py index 019b5a1a..d4f3cb8d 100644 --- a/opencodeblocks/scene/scene.py +++ b/opencodeblocks/scene/scene.py @@ -13,8 +13,6 @@ from PyQt5.QtGui import QColor, QPainter, QPen from PyQt5.QtWidgets import QGraphicsScene -from opencodeblocks import blocks - from opencodeblocks.core.serializable import Serializable from opencodeblocks.blocks.block import OCBBlock from opencodeblocks.graphics.edge import OCBEdge @@ -218,6 +216,7 @@ def create_block(self, data: OrderedDict, hashmap: dict = None, """ Create a new block from an OrderedDict """ block = None + blocks = __import__("opencodeblocks.blocks") block_constructor = None block_files = blocks.__dict__ From 4a58079e354f37033b449a0ffccef76aa8f46749 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Del=C3=A8gue?= Date: Sun, 12 Dec 2021 01:29:59 +0100 Subject: [PATCH 12/22] =?UTF-8?q?=F0=9F=8E=89=20The=20container=20node=20w?= =?UTF-8?q?orks=20!?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- opencodeblocks/blocks/containerblock.py | 21 +++++++++++++++++---- opencodeblocks/graphics/view.py | 6 ++++++ tests/integration/blocks/test_flow.py | 2 +- 3 files changed, 24 insertions(+), 5 deletions(-) diff --git a/opencodeblocks/blocks/containerblock.py b/opencodeblocks/blocks/containerblock.py index 7dfa4567..3e52fb7b 100644 --- a/opencodeblocks/blocks/containerblock.py +++ b/opencodeblocks/blocks/containerblock.py @@ -2,6 +2,7 @@ Exports OCBContainerBlock. """ +from PyQt5.QtGui import QColor from PyQt5.QtWidgets import QVBoxLayout from opencodeblocks.blocks.block import OCBBlock @@ -14,11 +15,23 @@ class OCBContainerBlock(OCBBlock): def __init__(self, **kwargs): super().__init__(**kwargs) + # Defer import to prevent circular dependency. + # Due to the overall structure of the code, this cannot be removed, as the + # scene should be able to serialize blocks. + # This is not due to bad code design and should not be removed. + from opencodeblocks.graphics.view import OCBView # pylint: disable=cyclic-import + from opencodeblocks.scene.scene import OCBScene # pylint: disable=cyclic-import + self.layout = QVBoxLayout(self.root) + self.layout.setContentsMargins( + self.edge_size * 2, + self.title_widget.height() + self.edge_size * 2, + self.edge_size * 2, + self.edge_size * 2 + ) - #self.scene = OCBScene() - #self.scene.addHasBeenModifiedListener(self.updateTitle) - #self.view = OCBView(self.scene) - #self.layout.addWidget(self.view) + self.child_scene = OCBScene() + self.child_view = OCBView(self.child_scene) + self.layout.addWidget(self.child_view) self.holder.setWidget(self.root) diff --git a/opencodeblocks/graphics/view.py b/opencodeblocks/graphics/view.py index 9ace412f..152f192c 100644 --- a/opencodeblocks/graphics/view.py +++ b/opencodeblocks/graphics/view.py @@ -316,6 +316,12 @@ def retreiveBlockTypes(self) -> List[Tuple[str]]: def contextMenuEvent(self, event: QContextMenuEvent): """Displays the context menu when inside a view""" + super().contextMenuEvent(event) + # If somebody has already accepted the event, don't handle it. + if event.isAccepted(): + return + event.setAccepted(True) + menu = QMenu(self) actionPool = [] for filepath, block_name in self.retreiveBlockTypes(): diff --git a/tests/integration/blocks/test_flow.py b/tests/integration/blocks/test_flow.py index 22a297d7..9b2a947d 100644 --- a/tests/integration/blocks/test_flow.py +++ b/tests/integration/blocks/test_flow.py @@ -52,7 +52,7 @@ def run_block(): block_to_run.run_left() msgQueue.run_lambda(run_block) - time.sleep(0.5) + time.sleep(1) msgQueue.check_equal(block_to_run.stdout.strip(), "6") msgQueue.check_equal(block_to_not_run.stdout.strip(), "") From c3707b5ff85d5dd2a24194ce337b285c31985acc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Del=C3=A8gue?= Date: Sun, 12 Dec 2021 01:38:13 +0100 Subject: [PATCH 13/22] =?UTF-8?q?=E2=9C=A8=20Useless=20changes=20to=20make?= =?UTF-8?q?=20the=20CI=20happy=20(that=20needlessly=20steal=20the=20time?= =?UTF-8?q?=20of=20the=20developer=20that=20might=20have=20been=20better?= =?UTF-8?q?=20spent=20implementing=20useful=20features=20or=20resolving=20?= =?UTF-8?q?bugs)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- opencodeblocks/blocks/codeblock.py | 2 +- opencodeblocks/blocks/containerblock.py | 1 - opencodeblocks/graphics/widget.py | 1 + tests/integration/blocks/test_flow.py | 5 ++--- tests/integration/utils.py | 1 + 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/opencodeblocks/blocks/codeblock.py b/opencodeblocks/blocks/codeblock.py index 77d59f69..db6ce55a 100644 --- a/opencodeblocks/blocks/codeblock.py +++ b/opencodeblocks/blocks/codeblock.py @@ -234,7 +234,7 @@ def serialize(self): base_dict = super().serialize() base_dict["source"] = self.source base_dict["stdout"] = self.stdout - + return base_dict def deserialize( diff --git a/opencodeblocks/blocks/containerblock.py b/opencodeblocks/blocks/containerblock.py index 3e52fb7b..86ea2a13 100644 --- a/opencodeblocks/blocks/containerblock.py +++ b/opencodeblocks/blocks/containerblock.py @@ -2,7 +2,6 @@ Exports OCBContainerBlock. """ -from PyQt5.QtGui import QColor from PyQt5.QtWidgets import QVBoxLayout from opencodeblocks.blocks.block import OCBBlock diff --git a/opencodeblocks/graphics/widget.py b/opencodeblocks/graphics/widget.py index a3910319..0836b437 100644 --- a/opencodeblocks/graphics/widget.py +++ b/opencodeblocks/graphics/widget.py @@ -62,6 +62,7 @@ def save(self): self.scene.save(self.savepath) def saveAsJupyter(self): + """ Save the current graph notebook as a regular python notebook """ self.scene.save_to_ipynb(self.savepath) def load(self, filepath: str): diff --git a/tests/integration/blocks/test_flow.py b/tests/integration/blocks/test_flow.py index 9b2a947d..70858a05 100644 --- a/tests/integration/blocks/test_flow.py +++ b/tests/integration/blocks/test_flow.py @@ -7,7 +7,6 @@ import pyautogui import pytest -from pytestqt.qtbot import QtBot import time @@ -40,7 +39,7 @@ def setup(self): if item.title in titles: self.blocks_to_run[titles.index(item.title)] = item - def test_flow_left(self, qtbot: QtBot): + def test_flow_left(self): """ Correct flow when pressing left run """ def testing_run(msgQueue: CheckingQueue): @@ -52,7 +51,7 @@ def run_block(): block_to_run.run_left() msgQueue.run_lambda(run_block) - time.sleep(1) + time.sleep(3) msgQueue.check_equal(block_to_run.stdout.strip(), "6") msgQueue.check_equal(block_to_not_run.stdout.strip(), "") diff --git a/tests/integration/utils.py b/tests/integration/utils.py index 20ed92ea..0cb37a51 100644 --- a/tests/integration/utils.py +++ b/tests/integration/utils.py @@ -35,6 +35,7 @@ def stop(self): def start_app(obj): + """ Create a new app for testing """ obj.window = OCBWindow() obj.ocb_widget = OCBWidget() obj.subwindow = obj.window.mdiArea.addSubWindow(obj.ocb_widget) From 99ffc0c52697554ef4b3b3d67dea349d2719b781 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Del=C3=A8gue?= Date: Sun, 12 Dec 2021 01:45:55 +0100 Subject: [PATCH 14/22] =?UTF-8?q?=F0=9F=AA=B2=20Change=20CWD=20instead=20o?= =?UTF-8?q?f=20path.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- opencodeblocks/scene/scene.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/opencodeblocks/scene/scene.py b/opencodeblocks/scene/scene.py index d21981a0..d4b513b4 100644 --- a/opencodeblocks/scene/scene.py +++ b/opencodeblocks/scene/scene.py @@ -183,7 +183,7 @@ def load(self, filepath: str): # Add filepath to kernel path dir_path = repr(path.abspath(path.dirname(filepath))) - setup_path_code = f"__import__(\"sys\").path[0] = {dir_path}" + setup_path_code = f"__import__(\"os\").chdir({dir_path})" self.kernel.execute(setup_path_code) def load_from_json(self, filepath: str): From f2f7ce3ca4421f7447e515844b7fff5525c93dd2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Del=C3=A8gue?= Date: Sun, 12 Dec 2021 17:57:11 +0100 Subject: [PATCH 15/22] =?UTF-8?q?=E2=9C=A8=20Remove=20trailing=20whitespac?= =?UTF-8?q?e?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- opencodeblocks/scene/scene.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/opencodeblocks/scene/scene.py b/opencodeblocks/scene/scene.py index 0e073256..a1a7bf9c 100644 --- a/opencodeblocks/scene/scene.py +++ b/opencodeblocks/scene/scene.py @@ -183,13 +183,13 @@ def load(self, filepath: str): # Add filepath to kernel path dir_path = repr(path.abspath(path.dirname(filepath))) - setup_path_code = f"__import__(\"os\").chdir({dir_path})" + setup_path_code = f'__import__("os").chdir({dir_path})' self.kernel.execute(setup_path_code) def load_from_json(self, filepath: str) -> OrderedDict: """ Load the json data into an ordered dict - + Args: filepath: Path to the file to load. """ @@ -270,8 +270,8 @@ def deserialize( ): self.clear() hashmap = hashmap if hashmap is not None else {} - if restore_id and 'id' in data: - self.id = data['id'] + if restore_id and "id" in data: + self.id = data["id"] # Create blocks for block_data in data["blocks"]: From 3f83c586c1501bf0c5cf5762b371df0bdfd46f9e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Math=C3=AFs=20F=C3=A9d=C3=A9rico?= Date: Sun, 12 Dec 2021 19:10:26 +0100 Subject: [PATCH 16/22] :memo: Update linear_classifier --- examples/linear_classifier.ipyg | 200 +++++++++++--------------------- 1 file changed, 66 insertions(+), 134 deletions(-) diff --git a/examples/linear_classifier.ipyg b/examples/linear_classifier.ipyg index 3aa765eb..a02f6f4a 100644 --- a/examples/linear_classifier.ipyg +++ b/examples/linear_classifier.ipyg @@ -1,67 +1,13 @@ { "id": 2034509196736, "blocks": [ - { - "id": 2034509423808, - "title": "Imports", - "block_type": "OCBCodeBlock", - "splitter_pos": [ - 227, - 0 - ], - "position": [ - -837.0, - -95.0 - ], - "width": 501, - "height": 286, - "metadata": { - "title_metadata": { - "color": "white", - "font": "Ubuntu", - "size": 10 - } - }, - "sockets": [ - { - "id": 2034509424672, - "type": "input", - "position": [ - 0.0, - 53.0 - ], - "metadata": { - "color": "#FF55FFF0", - "linecolor": "#FF000000", - "linewidth": 1.0, - "radius": 10.0 - } - }, - { - "id": 2034509424816, - "type": "output", - "position": [ - 501.0, - 53.0 - ], - "metadata": { - "color": "#FF55FFF0", - "linecolor": "#FF000000", - "linewidth": 1.0, - "radius": 10.0 - } - } - ], - "source": "from matplotlib import pyplot as plt\r\nfrom sklearn import linear_model\r\nfrom random import seed, random", - "stdout": "" - }, { "id": 2034638878464, "title": "", "block_type": "OCBMarkdownBlock", "splitter_pos": [ 0, - 194 + 200 ], "position": [ -940.0, @@ -85,14 +31,14 @@ "block_type": "OCBCodeBlock", "splitter_pos": [ 0, - 403 + 272 ], "position": [ - 757.0, - 106.0 + 53.875000000000085, + 212.25 ], - "width": 663, - "height": 462, + "width": 439, + "height": 325, "metadata": { "title_metadata": { "color": "white", @@ -106,7 +52,7 @@ "type": "input", "position": [ 0.0, - 53.0 + 40.0 ], "metadata": { "color": "#FF55FFF0", @@ -119,8 +65,8 @@ "id": 2034686483328, "type": "output", "position": [ - 663.0, - 53.0 + 439.0, + 40.0 ], "metadata": { "color": "#FF55FFF0", @@ -130,23 +76,23 @@ } } ], - "source": "um = 10 * m\r\nub = 2 * b\r\nuy = [um*i+ub for i in x]\r\n\r\nplt.plot(x,uy)\r\nplt.scatter(x,y)", - "stdout": "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\n" + "source": "import matplotlib.pyplot as plt\r\n\r\num = 10 * m\r\nub = 2 * b\r\nuy = [um*i+ub for i in x]\r\n\r\nplt.plot(x,uy)\r\nplt.scatter(x,y)", + "stdout": "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\n" }, { "id": 2034723533728, "title": "Generate some data to plot", "block_type": "OCBCodeBlock", "splitter_pos": [ - 275, - 0 + 189, + 85 ], "position": [ - -211.0, - -101.0 + -929.7499999999999, + -121.31250000000003 ], - "width": 641, - "height": 334, + "width": 706, + "height": 327, "metadata": { "title_metadata": { "color": "white", @@ -160,7 +106,7 @@ "type": "input", "position": [ 0.0, - 53.0 + 40.0 ], "metadata": { "color": "#FF55FFF0", @@ -173,8 +119,8 @@ "id": 2034723534736, "type": "output", "position": [ - 641.0, - 53.0 + 706.0, + 40.0 ], "metadata": { "color": "#FF55FFF0", @@ -184,8 +130,8 @@ } } ], - "source": "seed(132)\r\nx = [3 + random() * 10 for i in range(40)]\r\ny = [4.15 * i + random() * 2 for i in x]", - "stdout": "" + "source": "from random import seed, random\r\n\r\nseed(132)\r\nx = [3 + random() * 10 for i in range(40)]\r\ny = [4.15 * i + random() * 2 for i in x]\r\n\r\nprint(f\"Generated {len(x)} examples\")", + "stdout": "Generated 40 examples\n" }, { "id": 2034723677808, @@ -193,8 +139,8 @@ "block_type": "OCBSliderBlock", "splitter_pos": [], "position": [ - -175.0, - 296.0 + -890.625, + 258.50000000000006 ], "width": 618, "height": 184, @@ -211,7 +157,7 @@ "type": "input", "position": [ 0.0, - 50.0 + 42.0 ], "metadata": { "color": "#FF55FFF0", @@ -225,7 +171,7 @@ "type": "output", "position": [ 618.0, - 50.0 + 42.0 ], "metadata": { "color": "#FF55FFF0", @@ -235,8 +181,7 @@ } } ], - "source": "b = 0.12", - "value": "0.12", + "value": "0.82", "var_name": "b" }, { @@ -245,8 +190,8 @@ "block_type": "OCBSliderBlock", "splitter_pos": [], "position": [ - -184.0, - 550.0 + -901.1874999999999, + 471.8750000000001 ], "width": 618, "height": 184, @@ -263,7 +208,7 @@ "type": "input", "position": [ 0.0, - 50.0 + 42.0 ], "metadata": { "color": "#FF55FFF0", @@ -277,7 +222,7 @@ "type": "output", "position": [ 618.0, - 50.0 + 42.0 ], "metadata": { "color": "#FF55FFF0", @@ -287,8 +232,7 @@ } } ], - "source": "m = 0.12", - "value": "0.12", + "value": "0.41", "var_name": "m" }, { @@ -297,14 +241,14 @@ "block_type": "OCBCodeBlock", "splitter_pos": [ 0, - 349 + 276 ], "position": [ - 784.0, - -676.0 + 782.4375000000001, + -676.0000000000001 ], - "width": 615, - "height": 408, + "width": 434, + "height": 329, "metadata": { "title_metadata": { "color": "white", @@ -318,7 +262,7 @@ "type": "input", "position": [ 0.0, - 53.0 + 40.0 ], "metadata": { "color": "#FF55FFF0", @@ -331,8 +275,8 @@ "id": 2034879163984, "type": "output", "position": [ - 615.0, - 53.0 + 434.0, + 40.0 ], "metadata": { "color": "#FF55FFF0", @@ -342,8 +286,8 @@ } } ], - "source": "my = reg.predict([[i] for i in x])\r\nprint(my)\r\nplt.plot(x,my)\r\nplt.scatter(x,y)", - "stdout": "iVBORw0KGgoAAAANSUhEUgAAAXAAAAD4CAYAAAD1jb0+AAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjUuMCwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8/fFQqAAAACXBIWXMAAAsTAAALEwEAmpwYAAAc6ElEQVR4nO3de3jU5Z338feXECRYNCqRxVCMR2rVEuxUracqWlFgFa2r21qqrS3rbtvHWksF66qPh0qLVVy363OhVPHReihi7GorsoiPh1ZtYhCoiFrEw4ASD6EeokD4Pn/MJGZOmUkyM7/5zXxe1+WV+d3zG+Y76vXhzj33wdwdEREJn0FBFyAiIv2jABcRCSkFuIhISCnARURCSgEuIhJSg4v5ZiNGjPCGhoZivqWISOi1tLS87e51ye1FDfCGhgaam5uL+ZYiIqFnZq+ma9cQiohISOUU4Ga2zsxWmtlyM2uOt11mZtF423Izm1TYUkVEpKe+DKEc4+5vJ7Vd5+7X5LMgERHJjYZQRERCKtcAd+BhM2sxs+k92n9gZivM7DdmtlO6F5rZdDNrNrPmtra2ARcsIiIxuQb4Ee5+EHAi8H0zOwq4EdgLaAQ2AL9K90J3n+fuEXeP1NWlzIIREZF+yinA3T0a/7kRuA842N3fcvdOd98G3AQcXLgyRUQkWdYAN7PtzWx412PgeGCVmY3qcdspwKrClCgiUtqaWqMcPvsR9pj5IIfPfoSm1mj3c2vbPuCGpS+xpXNb3t83l1koI4H7zKzr/t+6+0Nm9n/NrJHY+Pg64F/yXp2ISIm7uGkldzz1Gl0nK0TbO5i1aCXuzpLVb/GHlW8CcMpB9YzeaVhe3ztrgLv7WmBcmvZpea1ERCRkmlqjCeHdpWNLJ+ff81z39XVnjMt7eIOmEYqI9NucxWtSwrunEZ/ZjmtO+wLXLH4x7fDKQBV1LxQRkXKyvr0j43O7bD+Eiyfvx6xFK+nY0gl8OrwCMHV8/YDfXz1wEZF+2q22JuNz/z7l88xZvKY7vLt0bOlkzuI1eXl/BbiISD+t35TaAzfgm4eOYer4+ow99N567n2hIRQRkT56/d2POPKXy1La62trmDFxbPfwyG61NUTThHVvPfe+UICLiPRBw8wHE67PP25fzjtun7T3zpg4NmEMHKCmuooZE8fmpRYFuIhIDlpfe49T/utPCW3rZk/u9TVdPfE5i9ewvr2D3ZJ66AOlABcRySK51339PzdycmNuITx1fH3eAjuZAlxEJIMHV2zg+799NqEtW6+7mBTgIlJxmlqjWYc1knvdC8/9MpGGnYtZZlYKcBGpKE2t0V4X1/x62csp87RLqdfdkwJcRCpKpsU1v3zoBX509/KE9kd/cjQNI7YvYnV9owAXkbLWc7ikdlg17320Je196zd9nHBdqr3unhTgIlK2kodLMoV3T89dcjw7DqsudGl5oQAXkbKVbrgkk52GVdN6yfEFrii/tBeKiJStvuw5MmxI+PqzOVVsZuuA94FOYKu7R8xsZ+BuoIHYiTynu/t7hSlTRKRvmlqjDDKj03vbsftT+dpgqpj60gM/xt0b3T0Sv54JLHX3fYCl8WsRkcB1jX3nGt6Qvw2mimkgQygnAwvijxcAUwdcjYhIHvQ29l1lqW353GCqmHINcAceNrMWM5sebxvp7hvij98kdvhxCjObbmbNZtbc1tY2wHJFRLLLNBxiwN+unszcMxqpr63BiG0Be/WpBxZsv5JCynXU/gh3j5rZrsASM3uh55Pu7maW9ncVd58HzAOIRCK5/z4jItJPmYKma5ikkBtMFVNOAe7u0fjPjWZ2H3Aw8JaZjXL3DWY2CthYwDpFRIDe9zE5+5ZneHRN+t/0wzpM0pusAW5m2wOD3P39+OPjgcuB3wNnAbPjP+8vZKEiIr3tY5K8DP60g0bz57XvFGQf7lKRSw98JHCfmXXd/1t3f8jM/gLcY2bnAK8CpxeuTBGRzPuYJId3GJbB50PWAHf3tcC4NO3vAMcWoigRkXSyzdX+4YS9ueD48hom6Y1WYopIKHQtzOnNomejRaqmNCjARaTk5bowJ4yrKQcifIv/RaSiNLVGueCe53JaVRnG1ZQDoR64iJSsptYoF967IqfwLsdpgtmoBy4iJSt5dkmyKjO2uZftNMFsFOAiUnIeWvUm597e0us9NdVVoV0Cny8KcBEput5WUyafBp9OlVnFhzdoDFxEiqxrRkm0vQPn09WU31vQnBLec89opKa6KqGtprqKX50+ruLDG9QDF5Eiy7Sacsnqt7qvzzxkDFedcmDCa8p5SXx/KcBFpKCSh0uiWeZqJy+DL5edAwtBAS4iBZNu8ykj83av3zx0TNFqKwcaAxeRgkk3XNLbjO47nnqNptbKWg4/EApwESmYvi5td2KhL7lRgItIwfRnaXul7WcyEApwESmYsw7bPaVtsEHVoMy7ClbafiYDoS8xRaQg0i3Iqa+t4aPNW3nvoy1pX1OJ+5kMRM4BbmZVQDMQdfcpZnYr8BVgU/yWs919ed4rFJFQaWqNpuxhsvryE6gZEluQs0cvKy21urJv+tIDPw9YDezQo22Guy/Mb0kiElbpet1zz2jsDm8g41zw+toahXcf5TQGbmajgcnAzYUtR0TC6Id3tmbcw2TWopUJUwNnTBybdnm8hk76LtcvMecCPwW2JbVfZWYrzOw6M9su3QvNbLqZNZtZc1tb2wBKFZFS1DDzQf77ufUZn+/Y0pkwNXDq+HquPvVA6mtrMGI9bw2d9E/WIRQzmwJsdPcWMzu6x1OzgDeBIcA84ELg8uTXu/u8+PNEIpHsu7KLSCik63FnWmWZPDVQy+PzI5ce+OHASWa2DrgLmGBmt7v7Bo/5BLgFOLiAdYpICUkX3utmT844BVBTAwsjaw/c3WcR620T74H/xN2/aWaj3H2DmRkwFVhVwDpFpARkCu4uMyaOTdj7BDS+XUgDmQd+h5nVEfutaTlwbl4qEpGSlBzep31xNNf807iEtq5hEW3/WhzmORwWmi+RSMSbm5uL9n4iMnDZet1SeGbW4u6R5HatxBSRtD78ZCv7X7o4oe2/zjyISQeOCqgiSaYAF5EU6nWHgwJcRLqt3vB3Trz+8YS2x2Ycw5hdhgVUkfRGAS4igHrdYaQAF6lwtz/1Khc3Jc4CfvHKExkyWLtNlzoFuEgFU6873BTgIhVo2vynefyltxPaFNzhowAXKTNNrdFeF9Ko110+tJBHpIw0tUZTlrID1NZU096RegqOgjscMi3k0bcUImVkzuI1KeENpIT3iM8MUXiXAQW4SBnJ5UT3+toa3vlgM4fPfiThoAUJHwW4SBnJZdvWaHsHHv+ZfFqOhIsCXKSM9HXb1uTTciRcFOAiZWJTx5aU0+Bzkcuwi5QmBbhIGWiY+SDj/vfDCW1zz2hMOHeytqY67Wt1Wk54aR64SIiteKOdk/7zyYS2Z352LLsOHwqQMP873RRDnZYTbjkHuJlVAc1A1N2nmNkexM7I3AVoAaa5++bClCkiyfq6IEen5ZSfvvTAzwNWAzvEr38BXOfud5nZ/wHOAW7Mc30iFam31ZS3PvkKl/338wn3/+3nk6gaZFn/XJ0GX15yCnAzGw1MBq4Cfhw/yHgC8I34LQuAy1CAiwxY8lBH13Q/IO2XlFqQU7ly7YHPBX4KDI9f7wK0u/vW+PUbQNq/1s1sOjAdYMyYMf0uVKRSpFtN2bGlMyW8FdySdRaKmU0BNrp7S3/ewN3nuXvE3SN1dXX9+SNEKkou0/oU3gK59cAPB04ys0nAUGJj4NcDtWY2ON4LHw1oOZdIHuxWW0M0Q4gruKWnrAHu7rOAWQBmdjTwE3c/08x+B5xGbCbKWcD9hStTpHwlf2F5zOfquP2p1xLuGWRw7emNwRQoJWsgC3kuJPaF5svExsTn56ckkcrR9YVlz/1JksO7yoxvHDJGs0ckRZ8W8rj7o8Cj8cdrgYPzX5JI5ci0/WtPne7c2xIlsvvOCnFJoKX0IgHKdR8SbTol6SjARQLy7oeb6ct5WNp0SpJpLxSRAKRbBt/FIG2wa9MpSaYeuEgRtb72Xkp4//yUAxJ2DTzz0DHUVFcl3KNNpyQd9cBFiqS3zae+ccjuCe2R3XfWplOSlQJcpICaWqNc+vu/sinpUOFXrp5EbEuh9LTplORCAS5SIE2t0bSbT809o7HX8BbJlQJcpACm3PA4q6J/T/vcnMVr1LuWvFCAi+RZbzNMQNMBJX8U4CJ5ki24u2g6oOSLphGK5EFyeE/cfyRzz2jUdEApKPXARfop05eUyVu+ajqgFIoCXKQf7m15gwt+91xCW3WVMee0cQltmg4ohaQAF+mjTGPdWzpdM0ykqDQGLpKjjX//WDNMpKRk7YGb2VDgMWC7+P0L3f1SM7sV+AqwKX7r2e6+vEB1igRKM0ykFOUyhPIJMMHdPzCzauAJM/tj/LkZ7r6wcOWJBKvl1Xf52o1/TmgbteNQjt1vV+5tiSYcxqAZJlJsuZyJ6cAH8cvq+D992cZYJJQy9bo3bPqYe1uifO2L9Sx7oU0zTCQwOX2JaWZVQAuwN/Brd3/azP4VuMrMLgGWAjPd/ZPClSpSHPOfeIUrHni+13s6tnSy7IU2npw5oUhViaTKKcDdvRNoNLNa4D4zO4DYSfVvAkOAecQOOb48+bVmNh2YDjBmzJj8VC1SIOl63ZkOWNAXlhK0Ps1Ccfd2YBlwgrtv8JhPgFvIcMCxu89z94i7R+rq6gZcsEghnH3LMynhvW72ZNbNnpzxi0l9YSlByxrgZlYX73ljZjXAV4EXzGxUvM2AqcCqwpUpUjgNMx/k0TVt3deNn61NWE05Y+JYLYmXkpTLEMooYEF8HHwQcI+7P2Bmj5hZHbHfMJcD5xauTJH86+2EnJ66vpjUkngpNRabZFIckUjEm5ubi/Z+Ipkkh/f5x+3LecftE1A1Ir0zsxZ3jyS3aym9VJRce90iYaAAl4rwydZOxl78UELbbd85mKP21RfrEl4KcCl76nVLuVKAS9la397BYbMfSWh7cuYE6jX9T8qEAlzKknrdUgkU4FJWHn+pjWnzn0loW3PlCWw3uCrDK0TCSwEuZUO9bqk0CnAJvf9Y+hLXLnkxoU3BLZVAAS6hpl63VDIFuITS1278Ey2vvpfQpuCWSqMAl9BJ7nUftW8dt30n7WaYImVNAS6hoeESkUQKcCl57s4es/6Q0Hbx5P347pF7BlSRSGlQgEtJU69bJDMFuJSkjs2d7HdJ4uZTvzv3y3ypYeeAKhIpPQpwCVRTazTloIQf3b085T71ukVSZQ1wMxsKPAZsF79/obtfamZ7AHcBuxA7sX6au28uZLFSXppao8xatJKOLZ0ARNs7UsL7mZ8dy67DhwZQnUjpy+VQ40+ACe4+DmgETjCzQ4FfANe5+97Ae8A5BatSytKcxWu6wzud+toa/vTyO0WsSCRcsgZ4/OT5D+KX1fF/HJgALIy3LyB2sLFIzta3d/T6fLS9g1mLVtLUGi1SRSLhktMYePxA4xZgb+DXwN+AdnffGr/lDSDtCa9mNh2YDjBmzJiB1ishlW6sO5fTWDu2dDJn8RodICySRi5DKLh7p7s3AqOBg4HP5foG7j7P3SPuHqmr0/FVlahrrDva3oGTfqy7N9l66iKVqk+zUNy93cyWAV8Gas1scLwXPhrQ77kVLl0ve+r4+l7Huj+7Uw3r2z+m0zP3x3fTCToiaWXtgZtZnZnVxh/XAF8FVgPLgNPit50F3F+gGiUE0vWyu8avM/WgDXj8wgls6yW8a6qrmDFxbGGKFgm5XIZQRgHLzGwF8Bdgibs/AFwI/NjMXiY2lXB+4cqUUpeul901fp0pnrt61pl62FVmXH3qgRr/Fskg6xCKu68AxqdpX0tsPFwkYy87mqG9Z896xsSxCfPBu55XeIv0LqcvMUWyyTZOfe5X9qS+tgYjNr+7ZzhPHV/P1acemPF5EUnPvJfxx3yLRCLe3NxctPeT4kleVdmTlsGLDIyZtbh7JLldPXDJi4n7/0NKeP9s0n4Kb5EC0mZWMmDa8lUkGApw6bfX3/2II3+5LKFt5WXHM3xodUAViVQWBbj0i3rdIsFTgEufPPny25x589MJbWt/PolBgyygikQqlwJccpbc6x4yeBAvXnliQNWIiAJcgMz7mADc/PharnxwdcL9Gi4RCZ4CXNKejDNr0UqAlF0Dj//8SOZ9K2U6qogEQAEuGfcxSQ5v9bpFSosCXLLut33x5P347pF7FqkaEcmVAlzYrbYm46ZT6nWLlC4tpRcuOH7flLYhVYOYe0Zj8YsRkZypB17h0i3IqU+ahSIipUkBXqE+/GQr+1+6OKHtqVnH8g87Dg2oIhHpq6wBbmafBW4DRgIOzHP3683sMuB7QFv81ovc/Q+FKlTyR8vgRcpDLj3wrcAF7v6smQ0HWsxsSfy569z9msKVJ/mUbvOpF644gaHVVQFVJCIDkcuRahuADfHH75vZakCDoyGTrtc994xGhbdIiPVpFoqZNRA7H7NrN6MfmNkKM/uNme2U4TXTzazZzJrb2trS3SIF9NTad9KGN9B9aryIhFPOR6qZ2WeA/wdc5e6LzGwk8DaxcfErgFHu/p3e/gwdqVZcmYK7p/raGp6cOaEI1YhIfw3oSDUzqwbuBe5w90UA7v6Wu3e6+zbgJnRCfcm4/alXU8I702av2VZhikjpyhrgZmbAfGC1u1/bo31Uj9tOAVblvzzpi6bWKA0zH+Tipk//U/zb0XuxbvbkjKfGZztNXkRKVy6zUA4HpgErzWx5vO0i4Otm1khsCGUd8C8FqE9ydO7tLTy06s2EtprqKvYdORyAGRPHppwaX1NdxYyJY4tap4jkTy6zUJ4g/W/gmvNdIjKNdXds6WTO4jVMHV/fvaoy057fIhI+WokZYt+7rZklz7/V6z09x7h7BrmIhJ8CPIS2bXP2vCjxF6Bdh2/Hxvc/SblXY9wi5UsBHjJfvGIJ73y4OaFt3ezJKafqgMa4RcqdAjwk0m0+tfySr1I7bAiAxrhFKpACPARy3XxKY9wilUUBXsLeeO8jjvhF4uZTL111ItVVOodDRBTggWpqjWYc8kjudX9x9524918PC6JMESlRCvCAJH/pGG3vYNailbzy9odcv/SlhHtfuXoSsQWxIiKf0u/iAZmzeE3CjBGILbzpGd7fPryBdbMnK7xFJC31wAOSbRMpnZAjItmoBx6QTAtsamuqFd4ikhMFeECO2HtESltNdRWXnbR/ANWISBhpCCUA6eZ112vhjYj0kQK8iK568HluevyVhDYNl4hIfynAi6Bzm7NX0uZTT190LCN3GBpQRSJSDhTgedDbgpxp85/m8Zfe7r53l+2H0PLvXw2qVBEpI1kD3Mw+C9wGjCR2+s48d7/ezHYG7gYaiJ3Ic7q7v1e4UktTpgU5Wzq3MWPhioR7n798IsOG6O9MEcmPXGahbAUucPfPA4cC3zezzwMzgaXuvg+wNH5dcTItyEkO7/raGh7+a++HL4iI9EXWAHf3De7+bPzx+8BqoB44GVgQv20BMLVANZa0XE917+qZN7VGC1yRiFSKPs0DN7MGYDzwNDDS3TfEn3qT2BBLutdMN7NmM2tua2sbSK0lKdOCnHSL37vOqBQRyYecA9zMPgPcC/zI3f/e8zl3d2Lj4yncfZ67R9w9UldXN6BiS9E5R+yR0lZTXZX+Xwa599hFRLLJKcDNrJpYeN/h7ovizW+Z2aj486OAjYUpsXR94bLFXP7A8wlt9bU1XH3qgdRn6JnrjEoRyZdcZqEYMB9Y7e7X9njq98BZwOz4z/sLUmEJWhXdxJQbnkhoS7cgR2dUikgh5TKn7XBgGrDSzJbH2y4iFtz3mNk5wKvA6QWpsMQkL4P/43lHst+oHVLu0xmVIlJoFhu+Lo5IJOLNzc1Fe798WrZmI9++5S/d1yN32I6nLzouwIpEpFKYWYu7R5LbtaokC3dnj1mJy+D/PGsCo3bUWLaIBEsB3ou7//IaF967svv6iL1HcPt3DwmwIhGRTynA00i3+dSKy45nh6HVAVUkIpJKAZ7k2ofX8B+PvNx9Pe3Q3bli6gEBViQikp4CPK5jcyf7XfJQQtuLV57IkME6tEhESpMCHDjvrlbuX76++/qiSZ9j+lF7BViRiEh2FR3g7364mYOuWJLQ9srVk4itXRIRKW0VG+D/eMMTrIxu6r6+4evj+cdxuwVYkYhI35RtgGc6JWfd2x9y9DWPJtyrcylFJIzKciVm8ik5ENuHZHPnNjq3ffp5755+KIfsuUvB6xERGYiKWomZ6ZScntTrFpGwK8sA723P7SXnH8U+I4cXsRoRkcIoy0nOmfbcrq+tUXiLSNkouwB3d3bZfkhKu/biFpFyU1ZDKM+93s7Jv36y+3qnYdW0f7RFe3GLSFkqiwDfts055cY/8dzr7QDsOnw7Hr/wGLYbXBVsYSIiBZTLkWq/AaYAG939gHjbZcD3gK5j5i9y9z+k/xMK6/GX2pg2/5nu61u//SWOHrtrEKWIiBRVLj3wW4H/BG5Lar/O3a/Je0U52rx1G1+Zs4wNmz4G4MD6HWn6/uFUDdIyeBGpDFkD3N0fM7OGItSSVroVlVWDjB/e2dp9z6J/O4yDxuwUVIkiIoEYyBj4D8zsW0AzcIG7v5fuJjObDkwHGDNmTJ/eIHlFZbS9gx/dvbz7+eP225WbvhXR5lMiUpH6O43wRmAvoBHYAPwq043uPs/dI+4eqaur69ObpFtR2eV/fnwUN5/1JYW3iFSsfgW4u7/l7p3uvg24CTg4v2XFZFpRacDeu2pBjohUtn4FuJmN6nF5CrAqP+UkyrSiMlO7iEglyRrgZnYn8GdgrJm9YWbnAL80s5VmtgI4Bji/EMXNmDiWmurEudxaUSkiEpPLLJSvp2meX4BaUnStnEy3r7eISKUr+ZWYU8fXK7BFRNIou82sREQqhQJcRCSkFOAiIiGlABcRCSkFuIhISCnARURCyty9eG9m1ga8WrQ37JsRwNtBFxEgff7K/fyV/NkhHJ9/d3dP2UyqqAFeysys2d0jQdcRFH3+yv38lfzZIdyfX0MoIiIhpQAXEQkpBfin5gVdQMD0+StXJX92CPHn1xi4iEhIqQcuIhJSCnARkZBSgANmVmVmrWb2QNC1FJuZ1ZrZQjN7wcxWm9mXg66pmMzsfDP7q5mtMrM7zWxo0DUVkpn9xsw2mtmqHm07m9kSM3sp/nOnIGsspAyff078//8VZnafmdUGWGKfKMBjzgNWB11EQK4HHnL3zwHjqKB/D2ZWD/wvIOLuBwBVwD8HW1XB3QqckNQ2E1jq7vsAS+PX5epWUj//EuAAd/8C8CIwq9hF9VfFB7iZjQYmAzcHXUuxmdmOwFHET1hy983u3h5oUcU3GKgxs8HAMGB9wPUUlLs/Bryb1HwysCD+eAEwtZg1FVO6z+/uD7v71vjlU8DoohfWTxUf4MBc4KfAtoDrCMIeQBtwS3wI6WYz2z7ooorF3aPANcBrwAZgk7s/HGxVgRjp7hvij98ERgZZTMC+A/wx6CJyVdEBbmZTgI3u3hJ0LQEZDBwE3Oju44EPKe9fnxPEx3pPJvYX2W7A9mb2zWCrCpbH5hVX5NxiM/sZsBW4I+haclXRAQ4cDpxkZuuAu4AJZnZ7sCUV1RvAG+7+dPx6IbFArxTHAa+4e5u7bwEWAYcFXFMQ3jKzUQDxnxsDrqfozOxsYApwpodocUxFB7i7z3L30e7eQOzLq0fcvWJ6YO7+JvC6mY2NNx0LPB9gScX2GnComQ0zMyP2+SvmS9wefg+cFX98FnB/gLUUnZmdQGwY9SR3/yjoevqi5E+ll4L7IXCHmQ0B1gLfDrieonH3p81sIfAssV+dWwnxsupcmNmdwNHACDN7A7gUmA3cY2bnENvu+fTgKiysDJ9/FrAdsCT29zhPufu5gRXZB1pKLyISUhU9hCIiEmYKcBGRkFKAi4iElAJcRCSkFOAiIiGlABcRCSkFuIhISP1/JgDCeeTfFPIAAAAASUVORK5CYII=\n" + "source": "import matplotlib.pyplot as plt\r\nmy = reg.predict([[i] for i in x])\r\nprint(my)\r\nplt.plot(x,my)\r\nplt.scatter(x,y)", + "stdout": "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\n" }, { "id": 2034879286288, @@ -351,11 +295,11 @@ "block_type": "OCBCodeBlock", "splitter_pos": [ 0, - 214 + 220 ], "position": [ - 757.0, - 619.0 + 611.6875000000001, + 251.81250000000006 ], "width": 685, "height": 273, @@ -372,7 +316,7 @@ "type": "input", "position": [ 0.0, - 53.0 + 40.0 ], "metadata": { "color": "#FF55FFF0", @@ -386,7 +330,7 @@ "type": "output", "position": [ 685.0, - 53.0 + 40.0 ], "metadata": { "color": "#FF55FFF0", @@ -397,22 +341,22 @@ } ], "source": "print(\"Your manual regression: \")\r\nprint(f\"y = x * {um} + {ub}\")\r\n\r\nprint(\"Accuracy:\")\r\nu = sum([(y[i] - (x[i]*um+ub)) ** 2 for i in range(len(x))])\r\ny_mean = sum(y) / len(y)\r\nv = sum([(y_i - y_mean) ** 2 for y_i in y])\r\nprint(1 - u/v)\r\n", - "stdout": "Your manual regression: \ny = x * 0.8999999999999999 + 0.24\nAccuracy:\n-6.737344734867353\n" + "stdout": "Your manual regression: \ny = x * 3.9000000000000004 + 1.64\nAccuracy:\n0.9724161281048267\n" }, { "id": 2034886210608, "title": "Create a new linear model", "block_type": "OCBCodeBlock", "splitter_pos": [ - 335, - 0 + 90, + 85 ], "position": [ - 10.0, - -562.0 + -160.3125, + -374.50000000000006 ], - "width": 656, - "height": 394, + "width": 840, + "height": 228, "metadata": { "title_metadata": { "color": "white", @@ -426,7 +370,7 @@ "type": "input", "position": [ 0.0, - 53.0 + 40.0 ], "metadata": { "color": "#FF55FFF0", @@ -439,8 +383,8 @@ "id": 2034886211616, "type": "output", "position": [ - 656.0, - 53.0 + 840.0, + 40.0 ], "metadata": { "color": "#FF55FFF0", @@ -450,7 +394,7 @@ } } ], - "source": "reg = linear_model.LinearRegression()\r\nreg.fit([[i] for i in x],y)", + "source": "from sklearn import linear_model\r\nreg = linear_model.LinearRegression()\r\nreg.fit([[i] for i in x],y)", "stdout": "LinearRegression()" }, { @@ -459,14 +403,14 @@ "block_type": "OCBCodeBlock", "splitter_pos": [ 0, - 224 + 278 ], "position": [ - 800.0, - -219.0 + 767.1875000000002, + -279.9375 ], - "width": 670, - "height": 283, + "width": 816, + "height": 331, "metadata": { "title_metadata": { "color": "white", @@ -480,7 +424,7 @@ "type": "input", "position": [ 0.0, - 53.0 + 40.0 ], "metadata": { "color": "#FF55FFF0", @@ -493,8 +437,8 @@ "id": 2136886540896, "type": "output", "position": [ - 670.0, - 53.0 + 816.0, + 40.0 ], "metadata": { "color": "#FF55FFF0", @@ -509,18 +453,6 @@ } ], "edges": [ - { - "id": 2034686480592, - "path_type": "bezier", - "source": { - "block": 2034509423808, - "socket": 2034509424816 - }, - "destination": { - "block": 2034723533728, - "socket": 2034723534592 - } - }, { "id": 2034686599952, "path_type": "bezier", From 7c40d1a4e22e244e3393ec946e897961ab1a7ab3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Del=C3=A8gue?= Date: Sun, 12 Dec 2021 20:55:21 +0100 Subject: [PATCH 17/22] =?UTF-8?q?=E2=9C=A8=20Add=20@abstract=20decorator?= =?UTF-8?q?=20to=20abstract=20method.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Note that @abstractproperty should not be used here: https://docs.python.org/3.8/library/abc.html#abc.abstractproperty --- opencodeblocks/blocks/executableblock.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/opencodeblocks/blocks/executableblock.py b/opencodeblocks/blocks/executableblock.py index 63a82a58..5f228142 100644 --- a/opencodeblocks/blocks/executableblock.py +++ b/opencodeblocks/blocks/executableblock.py @@ -1,6 +1,7 @@ """ Module for the executable block class """ from typing import List, OrderedDict +from abc import abstractmethod from networkx.algorithms.traversal.breadth_first_search import bfs_edges @@ -129,11 +130,13 @@ def reset_has_been_run(self): self.has_been_run = False @property + @abstractmethod def source(self) -> str: """Source code""" raise NotImplementedError("source(self) should be overriden") @source.setter + @abstractmethod def source(self, value: str): raise NotImplementedError("source(self) should be overriden") From 9907f1135e56979e90a3b6e8b329578d5125387c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Del=C3=A8gue?= Date: Sun, 12 Dec 2021 21:54:02 +0100 Subject: [PATCH 18/22] =?UTF-8?q?=E2=98=94=20Add=20tests=20for=20the=20CWD?= =?UTF-8?q?=20bug.=20Make=20test=20quicker=20but=20waiting=20properly=20fo?= =?UTF-8?q?r=20a=20block=20to=20complete=20instead=20of=20just=20waiting?= =?UTF-8?q?=203=20seconds.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/assets/data.txt | 7 + tests/assets/example_graph1.ipyg | 391 +-------------------- tests/integration/blocks/test_block.py | 3 + tests/integration/blocks/test_codeblock.py | 40 +++ tests/integration/blocks/test_flow.py | 9 +- tests/integration/utils.py | 1 - 6 files changed, 74 insertions(+), 377 deletions(-) create mode 100644 tests/assets/data.txt diff --git a/tests/assets/data.txt b/tests/assets/data.txt new file mode 100644 index 00000000..0bce9e3a --- /dev/null +++ b/tests/assets/data.txt @@ -0,0 +1,7 @@ +1 +2 +3 +4 +5 +6 +7 \ No newline at end of file diff --git a/tests/assets/example_graph1.ipyg b/tests/assets/example_graph1.ipyg index 7158ae33..c96688ed 100644 --- a/tests/assets/example_graph1.ipyg +++ b/tests/assets/example_graph1.ipyg @@ -2,227 +2,33 @@ "id": 2205665405400, "blocks": [ { - "id": 2443477874008, - "title": "Model Train", + "id": 1523300599264, + "title": "test1", "block_type": "OCBCodeBlock", - "source": "print(\"training \")\r\nmodel.fit(x=x_train,y=y_train, epochs=10)\r\n\r\n", - "stdout": "", - "image": "", - "position": [ - 2202.0742187499986, - -346.82031249999983 - ], - "width": 1644.8125, - "height": 481.4375, - "metadata": { - "title_metadata": { - "color": "white", - "font": "Ubuntu", - "size": 10, - "padding": 4.0 - } - }, - "sockets": [ - { - "id": 2443477875016, - "type": "input", - "position": [ - 0.0, - 42.0 - ], - "metadata": { - "color": "#e02c2c", - "linecolor": "#FF000000", - "linewidth": 1.0, - "radius": 6.0 - } - }, - { - "id": 2443477875160, - "type": "output", - "position": [ - 1644.8125, - 42.0 - ], - "metadata": { - "color": "#35bc31", - "linecolor": "#FF000000", - "linewidth": 1.0, - "radius": 6.0 - } - } - ] - }, - { - "id": 2443477924600, - "title": "Keras Model Predict", - "block_type": "OCBCodeBlock", - "source": "prediction = model.predict(x_test[9].reshape(1, 28, 28, 1))", - "stdout": "", - "image": "", - "position": [ - 4207.046874999999, - -244.57812499999991 - ], - "width": 1239.6875, - "height": 305.9374999999999, - "metadata": { - "title_metadata": { - "color": "white", - "font": "Ubuntu", - "size": 10, - "padding": 4.0 - } - }, - "sockets": [ - { - "id": 2443477925608, - "type": "input", - "position": [ - 0.0, - 42.0 - ], - "metadata": { - "color": "#FF55FFF0", - "linecolor": "#FF000000", - "linewidth": 1.0, - "radius": 6.0 - } - }, - { - "id": 2443477925752, - "type": "output", - "position": [ - 1239.6875, - 42.0 - ], - "metadata": { - "color": "#FF55FFF0", - "linecolor": "#FF000000", - "linewidth": 1.0, - "radius": 6.0 - } - } - ] - }, - { - "id": 2443477997032, - "title": "Keras Model eval", - "block_type": "OCBCodeBlock", - "source": "model.evaluate(x_test, y_test)\r\n", - "stdout": "", - "image": "", - "position": [ - 4204.085937499997, - -707.0546874999997 - ], - "width": 1628.375, - "height": 209.875, - "metadata": { - "title_metadata": { - "color": "white", - "font": "Ubuntu", - "size": 10, - "padding": 4.0 - } - }, - "sockets": [ - { - "id": 2443477997896, - "type": "input", - "position": [ - 0.0, - 42.0 - ], - "metadata": { - "color": "#FF55FFF0", - "linecolor": "#FF000000", - "linewidth": 1.0, - "radius": 6.0 - } - }, - { - "id": 2443477998184, - "type": "output", - "position": [ - 1628.375, - 42.0 - ], - "metadata": { - "color": "#FF55FFF0", - "linecolor": "#FF000000", - "linewidth": 1.0, - "radius": 6.0 - } - } - ] - }, - { - "id": 2443478874872, - "title": "Load MNIST Dataset", - "block_type": "OCBCodeBlock", - "source": "print(\"Hello, world\")\r\nfrom tensorflow.keras.datasets import mnist\r\n(x_train, y_train), (x_test, y_test) = mnist.load_data()\r\n", - "stdout": "", - "image": "", - "position": [ - -535.75, - -687.0625 + "splitter_pos": [ + 292, + 0 ], - "width": 739.5, - "height": 343.5, - "metadata": { - "title_metadata": { - "color": "white", - "font": "Ubuntu", - "size": 10, - "padding": 4.0 - } - }, - "sockets": [ - { - "id": 2443478910728, - "type": "output", - "position": [ - 739.5, - 42.0 - ], - "metadata": { - "color": "#FF55FFF0", - "linecolor": "#FF000000", - "linewidth": 1.0, - "radius": 6.0 - } - } - ] - }, - { - "id": 2443478982728, - "title": "Normalize Image Dataset", - "block_type": "OCBCodeBlock", - "source": "x_train = x_train.astype('float32') / 255.0\r\nx_test = x_test.astype('float32') / 255.0\r\n\r\n\r\nx_train = x_train.reshape(x_train.shape[0], 28, 28, 1)\r\nx_test = x_test.reshape(x_test.shape[0], 28, 28, 1)\r\n\r\nprint('train:', x_train.shape, '|test:', x_test.shape)", - "stdout": "", - "image": "", "position": [ - 281.2500000000002, - -149.74999999999977 + 1192.0, + 292.79999999999995 ], - "width": 705.7499999999998, - "height": 357.25, + "width": 707, + "height": 351, "metadata": { "title_metadata": { "color": "white", "font": "Ubuntu", - "size": 10, - "padding": 4.0 + "size": 10 } }, "sockets": [ { - "id": 2443478983592, + "id": 1523350963536, "type": "input", "position": [ 0.0, - 42.0 + 45.0 ], "metadata": { "color": "#FF55FFF0", @@ -232,11 +38,11 @@ } }, { - "id": 2443478983880, + "id": 1523350963680, "type": "output", "position": [ - 705.7499999999998, - 42.0 + 707.0, + 45.0 ], "metadata": { "color": "#FF55FFF0", @@ -245,171 +51,10 @@ "radius": 6.0 } } - ] - }, - { - "id": 2443479017656, - "title": "Build Keras CNN", - "block_type": "OCBCodeBlock", - "source": "import tensorflow as tf\r\nfrom tensorflow.keras.layers import Dense, Flatten, Conv2D, MaxPooling2D, Dropout\r\nfrom tensorflow.keras.models import Sequential\r\n\r\nmodel = Sequential()\r\nmodel.add(Conv2D(28, kernel_size=(3,3), input_shape=x_train.shape[1:]))\r\nmodel.add(MaxPooling2D(pool_size=(2, 2)))\r\nmodel.add(Flatten())\r\nmodel.add(Dense(128, activation=tf.nn.relu))\r\nmodel.add(Dropout(0.2))\r\nmodel.add(Dense(10,activation=tf.nn.softmax))\r\nprint(\"..\")\r\nmodel.compile(optimizer='adam', \r\n loss='sparse_categorical_crossentropy', \r\n metrics=['accuracy'])\r\n", - "stdout": "", - "image": "", - "position": [ - 1316.25, - -517.6249999999998 ], - "width": 680.0, - "height": 468.75, - "metadata": { - "title_metadata": { - "color": "white", - "font": "Ubuntu", - "size": 10, - "padding": 4.0 - } - }, - "sockets": [ - { - "id": 2443479018520, - "type": "input", - "position": [ - 0.0, - 42.0 - ], - "metadata": { - "color": "#FF55FFF0", - "linecolor": "#FF000000", - "linewidth": 1.0, - "radius": 6.0 - } - }, - { - "id": 2443479018808, - "type": "output", - "position": [ - 680.0, - 42.0 - ], - "metadata": { - "color": "#FF55FFF0", - "linecolor": "#FF000000", - "linewidth": 1.0, - "radius": 6.0 - } - } - ] - }, - { - "id": 2828158533848, - "title": "Plot Image Dataset Example", - "block_type": "OCBCodeBlock", - "source": "import matplotlib.pyplot as plt\r\nimport numpy as np\r\n\r\n# 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[0]))\r\n", - "stdout": "", - "image": "", - "position": [ - 433.375, - -1221.75 - ], - "width": 778.9375, - "height": 763.25, - "metadata": { - "title_metadata": { - "color": "white", - "font": "Ubuntu", - "size": 10, - "padding": 4.0 - } - }, - "sockets": [ - { - "id": 2828158535432, - "type": "input", - "position": [ - 0.0, - 42.0 - ], - "metadata": { - "color": "#FF55FFF0", - "linecolor": "#FF000000", - "linewidth": 1.0, - "radius": 6.0 - } - } - ] + "source": "content = open(\"data.txt\").read()\r\nprint(content)", + "stdout": "" } ], - "edges": [ - { - "id": 1643571233840, - "path_type": "bezier", - "source": { - "block": 2443479017656, - "socket": 2443479018808 - }, - "destination": { - "block": 2443477874008, - "socket": 2443477875016 - } - }, - { - "id": 2006783605056, - "path_type": "bezier", - "source": { - "block": 2443478874872, - "socket": 2443478910728 - }, - "destination": { - "block": 2828158533848, - "socket": 2828158535432 - } - }, - { - "id": 2006783606064, - "path_type": "bezier", - "source": { - "block": 2443477874008, - "socket": 2443477875160 - }, - "destination": { - "block": 2443477924600, - "socket": 2443477925608 - } - }, - { - "id": 2111730223424, - "path_type": "bezier", - "source": { - "block": 2443478982728, - "socket": 2443478983880 - }, - "destination": { - "block": 2443479017656, - "socket": 2443479018520 - } - }, - { - "id": 2111730224144, - "path_type": "bezier", - "source": { - "block": 2443477874008, - "socket": 2443477875160 - }, - "destination": { - "block": 2443477997032, - "socket": 2443477997896 - } - }, - { - "id": 2111730844864, - "path_type": "bezier", - "source": { - "block": 2443478874872, - "socket": 2443478910728 - }, - "destination": { - "block": 2443478982728, - "socket": 2443478983592 - } - } - ] + "edges": [] } \ No newline at end of file diff --git a/tests/integration/blocks/test_block.py b/tests/integration/blocks/test_block.py index b07cecc1..1b843b11 100644 --- a/tests/integration/blocks/test_block.py +++ b/tests/integration/blocks/test_block.py @@ -73,3 +73,6 @@ def testing_drag(msgQueue: CheckingQueue): msgQueue.stop() apply_function_inapp(self.window, testing_drag) + + def test_finish(self): + self.window.close() \ No newline at end of file diff --git a/tests/integration/blocks/test_codeblock.py b/tests/integration/blocks/test_codeblock.py index 47a8cd28..e126d5be 100644 --- a/tests/integration/blocks/test_codeblock.py +++ b/tests/integration/blocks/test_codeblock.py @@ -6,6 +6,7 @@ """ import time +import os import pyautogui import pytest @@ -57,3 +58,42 @@ def testing_run(msgQueue: CheckingQueue): msgQueue.stop() apply_function_inapp(self.window, testing_run) + + + def test_run_block_with_path(self): + """ runs blocks with the correct working directory for the kernel """ + file_example_path = "./tests/assets/example_graph1.ipyg" + asset_path = "./tests/assets/data.txt" + self.ocb_widget.scene.load(os.path.abspath(file_example_path)) + + def testing_path(msgQueue: CheckingQueue): + block_of_test: OCBCodeBlock = None + for item in self.ocb_widget.scene.items(): + if isinstance(item,OCBCodeBlock) and item.title == "test1": + block_of_test = item + print(item.title) + break + msgQueue.check_equal(block_of_test is not None, True, "example_graph1 contains a block titled test1") + + def run_block(): + block_of_test.run_code() + + msgQueue.run_lambda(run_block) + time.sleep(0.1) # wait for the lambda to complete. + while block_of_test.is_running: + time.sleep(0.1) # wait for the execution to finish. + + file_content = open(asset_path).read() + + print('"'+block_of_test.stdout.strip()+'"') + print('"'+file_content+'"') + + msgQueue.check_equal(block_of_test.stdout.strip(), file_content, "The asset file is read properly") + + msgQueue.stop() + + apply_function_inapp(self.window, testing_path) + + + def test_finish(self): + self.window.close() \ No newline at end of file diff --git a/tests/integration/blocks/test_flow.py b/tests/integration/blocks/test_flow.py index 70858a05..7a830310 100644 --- a/tests/integration/blocks/test_flow.py +++ b/tests/integration/blocks/test_flow.py @@ -10,8 +10,6 @@ import time -from PyQt5.QtCore import QPointF - from opencodeblocks.blocks.codeblock import OCBCodeBlock from opencodeblocks.graphics.window import OCBWindow from opencodeblocks.graphics.widget import OCBWidget @@ -51,10 +49,15 @@ def run_block(): block_to_run.run_left() msgQueue.run_lambda(run_block) - time.sleep(3) + time.sleep(0.1) + while block_to_run.is_running: + time.sleep(0.1) # wait for the execution to finish. msgQueue.check_equal(block_to_run.stdout.strip(), "6") msgQueue.check_equal(block_to_not_run.stdout.strip(), "") msgQueue.stop() apply_function_inapp(self.window, testing_run) + + def test_finish(self): + self.window.close() \ No newline at end of file diff --git a/tests/integration/utils.py b/tests/integration/utils.py index 2054b4d2..9c5af1ec 100644 --- a/tests/integration/utils.py +++ b/tests/integration/utils.py @@ -62,4 +62,3 @@ def apply_function_inapp(window: OCBWindow, run_func: Callable): elif msg[0] == RUN_MSG: msg[1](*msg[2], **msg[3]) t.join() - window.close() From b84932756f4b1cbd8f76b0f92a6902b23ea1e67c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Del=C3=A8gue?= Date: Sun, 12 Dec 2021 22:05:11 +0100 Subject: [PATCH 19/22] =?UTF-8?q?=E2=98=94=20Add=20test=20to=20prevent=20#?= =?UTF-8?q?112=20regressions.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/integration/blocks/test_flow.py | 30 ++++++++++++++++++++++++--- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/tests/integration/blocks/test_flow.py b/tests/integration/blocks/test_flow.py index 7a830310..dff8311e 100644 --- a/tests/integration/blocks/test_flow.py +++ b/tests/integration/blocks/test_flow.py @@ -5,9 +5,7 @@ Integration tests for the OCBCodeBlocks. """ -import pyautogui import pytest - import time from opencodeblocks.blocks.codeblock import OCBCodeBlock @@ -37,8 +35,34 @@ def setup(self): if item.title in titles: self.blocks_to_run[titles.index(item.title)] = item + def test_duplicated_run(self): + """ Don't run a block twice when the execution flows """ + for b in self.blocks_to_run: + b.stdout = "" + + def testing_no_duplicates(msgQueue: CheckingQueue): + + block_to_run: OCBCodeBlock = self.blocks_to_run[0] + + def run_block(): + block_to_run.run_right() + + msgQueue.run_lambda(run_block) + time.sleep(0.1) + while block_to_run.is_running: + time.sleep(0.1) # wait for the execution to finish. + + # 6 and not 6\n6 + msgQueue.check_equal(block_to_run.stdout.strip(), "6") + msgQueue.stop() + + apply_function_inapp(self.window, testing_no_duplicates) + def test_flow_left(self): """ Correct flow when pressing left run """ + + for b in self.blocks_to_run: + b.stdout = "" def testing_run(msgQueue: CheckingQueue): @@ -58,6 +82,6 @@ def run_block(): msgQueue.stop() apply_function_inapp(self.window, testing_run) - + def test_finish(self): self.window.close() \ No newline at end of file From 76c5786585b6a88b1bcae83ab31485542461cc5d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Del=C3=A8gue?= Date: Mon, 20 Dec 2021 16:25:48 +0100 Subject: [PATCH 20/22] =?UTF-8?q?=E2=98=94=F0=9F=AA=B2=20Change=20properly?= =?UTF-8?q?=20catch=20error=20in=20tests.=20Remove=20code=20that=20was=20w?= =?UTF-8?q?rongfully=20added=20by=20git=20during=20a=20merge.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- opencodeblocks/blocks/codeblock.py | 82 --------------------------- tests/integration/blocks/test_flow.py | 26 +++++---- tests/integration/test_window.py | 10 ++-- tests/integration/utils.py | 40 ++++++++++++- 4 files changed, 56 insertions(+), 102 deletions(-) diff --git a/opencodeblocks/blocks/codeblock.py b/opencodeblocks/blocks/codeblock.py index 07ae52ca..db6ce55a 100644 --- a/opencodeblocks/blocks/codeblock.py +++ b/opencodeblocks/blocks/codeblock.py @@ -126,88 +126,6 @@ def execution_finished(self): self.run_button.setText(">") self.run_all_button.setText(">>") - def has_input(self) -> bool: - """Checks whether a block has connected input blocks""" - for input_socket in self.sockets_in: - if len(input_socket.edges) != 0: - return True - return False - - def has_output(self) -> bool: - """Checks whether a block has connected output blocks""" - for output_socket in self.sockets_out: - if len(output_socket.edges) != 0: - return True - return False - - def _interrupt_execution(self): - """Interrupt an execution, reset the blocks in the queue""" - for block, _ in self.source_editor.kernel.execution_queue: - # Reset the blocks that have not been run - block.reset_buttons() - block.has_been_run = False - # Clear the queue - self.source_editor.kernel.execution_queue = [] - # Interrupt the kernel - self.source_editor.kernel.kernel_manager.interrupt_kernel() - - def run_left(self, in_right_button=False): - """ - Run all of the block's dependencies and then run the block - """ - # If the user presses left run when running, cancel the execution - if self.run_button.text() == "..." and not in_right_button: - self._interrupt_execution() - return - - # If no dependencies - if not self.has_input(): - return self.run_code() - - # Create the graph from the scene - graph = self.scene().create_graph() - # BFS through the input graph - edges = bfs_edges(graph, self, reverse=True) - # Run the blocks found except self - blocks_to_run: List["OCBCodeBlock"] = [v for _, v in edges] - for block in blocks_to_run[::-1]: - if not block.has_been_run: - block.run_code() - - if in_right_button: - # If run_left was called inside of run_right - # self is not necessarily the block that was clicked - # which means that self does not need to be run - if not self.has_been_run: - self.run_code() - else: - # On the contrary if run_left was called outside of run_right - # self is the block that was clicked - # so self needs to be run - self.run_code() - - def run_right(self): - """Run all of the output blocks and all their dependencies""" - # If the user presses right run when running, cancel the execution - if self.run_all_button.text() == "...": - self._interrupt_execution() - return - - # If no output, run left - if not self.has_output(): - return self.run_left(in_right_button=True) - - # Same as run_left but instead of running the blocks, we'll use run_left - graph = self.scene().create_graph() - edges = bfs_edges(graph, self) - blocks_to_run: List["OCBCodeBlock"] = [self] + [v for _, v in edges] - for block in blocks_to_run[::-1]: - block.run_left(in_right_button=True) - - def reset_has_been_run(self): - """Reset has_been_run, is called when the output is an error""" - self.has_been_run = False - def update_title(self): """Change the geometry of the title widget""" self.title_widget.setGeometry( diff --git a/tests/integration/blocks/test_flow.py b/tests/integration/blocks/test_flow.py index 8d408d53..3121567c 100644 --- a/tests/integration/blocks/test_flow.py +++ b/tests/integration/blocks/test_flow.py @@ -9,10 +9,8 @@ import time from opencodeblocks.blocks.codeblock import OCBCodeBlock -from opencodeblocks.graphics.window import OCBWindow -from opencodeblocks.graphics.widget import OCBWidget -from tests.integration.utils import apply_function_inapp, CheckingQueue +from tests.integration.utils import apply_function_inapp, CheckingQueue, start_app class TestCodeBlocks: @@ -20,20 +18,17 @@ class TestCodeBlocks: @pytest.fixture(autouse=True) def setup(self): """ Setup reused variables. """ - self.window = OCBWindow() - self.ocb_widget = OCBWidget() - self.subwindow = self.window.mdiArea.addSubWindow(self.ocb_widget) - self.subwindow.show() + start_app(self) self.ocb_widget.scene.load("tests/assets/flow_test.ipyg") - titles = ["Test flow 5", "Test flow 4", "Test no connection 1", + self.titles = ["Test flow 5", "Test flow 4", "Test no connection 1", "Test input only 2", "Test output only 1"] self.blocks_to_run = [None]*5 for item in self.ocb_widget.scene.items(): if isinstance(item, OCBCodeBlock): - if item.title in titles: - self.blocks_to_run[titles.index(item.title)] = item + if item.title in self.titles: + self.blocks_to_run[self.titles.index(item.title)] = item def test_duplicated_run(self): """ Don't run a block twice when the execution flows """ @@ -95,8 +90,13 @@ def testing_run(msgQueue: CheckingQueue): def run_block(): block_to_run.run_left() + print("About to run !") + msgQueue.run_lambda(run_block) - time.sleep(0.5) + time.sleep(0.1) + while block_to_run.is_running: + print("wait ...") + time.sleep(0.1) msgQueue.check_equal(block_to_run.stdout.strip(), "1") msgQueue.stop() @@ -116,7 +116,9 @@ def run_block(): block_to_run.run_right() msgQueue.run_lambda(run_block) - time.sleep(0.5) + time.sleep(0.1) + while block_to_run.is_running: + time.sleep(0.1) # Just check that it doesn't crash msgQueue.stop() diff --git a/tests/integration/test_window.py b/tests/integration/test_window.py index 2544b81f..780f041c 100644 --- a/tests/integration/test_window.py +++ b/tests/integration/test_window.py @@ -18,14 +18,14 @@ def setup(self, mocker: MockerFixture): """Setup reused variables.""" self.window = OCBWindow() - def test_window_close(self, qtbot): - """closes""" - self.window.close() - - def test_open_file(self): + def test_open_file(self, qtbot): """loads files""" wnd = OCBWindow() file_example_path = "./tests/assets/example_graph1.ipyg" subwnd = wnd.createNewMdiChild(os.path.abspath(file_example_path)) subwnd.show() wnd.close() + + def test_window_close(self, qtbot): + """closes""" + self.window.close() \ No newline at end of file diff --git a/tests/integration/utils.py b/tests/integration/utils.py index 9c5af1ec..89f41067 100644 --- a/tests/integration/utils.py +++ b/tests/integration/utils.py @@ -5,14 +5,17 @@ Utilities functions for integration testing. """ -import os -import asyncio from typing import Callable +import os +import asyncio import threading +import time from queue import Queue + from qtpy.QtWidgets import QApplication import pytest_check as check +import warnings from opencodeblocks.graphics.widget import OCBWidget from opencodeblocks.graphics.window import OCBWindow @@ -32,6 +35,27 @@ def run_lambda(self, func: Callable, *args, **kwargs): def stop(self): self.put([STOP_MSG]) +class ExceptionForwardingThread(threading.Thread): + """ A Thread class that forwards the exceptions to the calling thread """ + def __init__(self, *args, **kwargs): + """ Create an exception forwarding thread """ + super().__init__(*args, **kwargs) + self.e = None + + def run(self): + """ Code ran in another thread """ + try: + super().run() + except Exception as e: + self.e = e + + def join(self): + """ Used to sync the thread with the caller """ + super().join() + print("except: ",self.e) + if self.e != None: + raise self.e + def start_app(obj): """ Create a new app for testing """ @@ -47,11 +71,14 @@ def apply_function_inapp(window: OCBWindow, run_func: Callable): QApplication.processEvents() msgQueue = CheckingQueue() - t = threading.Thread(target=run_func, args=(msgQueue,)) + t = ExceptionForwardingThread(target=run_func, args=(msgQueue,)) t.start() stop = False + deadCounter = 0 + while not stop: + time.sleep(1 / 30) # 30 fps QApplication.processEvents() if not msgQueue.empty(): msg = msgQueue.get() @@ -61,4 +88,11 @@ def apply_function_inapp(window: OCBWindow, run_func: Callable): stop = True elif msg[0] == RUN_MSG: msg[1](*msg[2], **msg[3]) + + if not t.is_alive() and not stop: + deadCounter += 1 + if deadCounter >= 3: + # Test failed, close was not called + warnings.warn("Warning: you need to call CheckingQueue.stop() at the end of your test !") + break t.join() From df5198564f1b169df360c6c19ee657c40f37c379 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Del=C3=A8gue?= Date: Mon, 20 Dec 2021 16:43:05 +0100 Subject: [PATCH 21/22] =?UTF-8?q?=E2=9C=A8=20Black=20formatting=20?= =?UTF-8?q?=E2=98=94=20Test=20reliability=20improvements?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- opencodeblocks/blocks/containerblock.py | 10 +++--- opencodeblocks/blocks/sliderblock.py | 6 ++-- opencodeblocks/graphics/widget.py | 2 +- opencodeblocks/graphics/worker.py | 2 +- tests/integration/blocks/test_block.py | 4 +-- tests/integration/blocks/test_codeblock.py | 39 ++++++++++++---------- tests/integration/blocks/test_flow.py | 26 +++++++++------ tests/integration/test_window.py | 2 +- tests/integration/utils.py | 21 +++++++----- tests/unit/scene/test_ipynb_conversion.py | 5 +-- 10 files changed, 67 insertions(+), 50 deletions(-) diff --git a/opencodeblocks/blocks/containerblock.py b/opencodeblocks/blocks/containerblock.py index 86ea2a13..df00a483 100644 --- a/opencodeblocks/blocks/containerblock.py +++ b/opencodeblocks/blocks/containerblock.py @@ -8,7 +8,7 @@ class OCBContainerBlock(OCBBlock): """ - A block that can contain other blocks. + A block that can contain other blocks. """ def __init__(self, **kwargs): @@ -18,15 +18,17 @@ def __init__(self, **kwargs): # Due to the overall structure of the code, this cannot be removed, as the # scene should be able to serialize blocks. # This is not due to bad code design and should not be removed. - from opencodeblocks.graphics.view import OCBView # pylint: disable=cyclic-import - from opencodeblocks.scene.scene import OCBScene # pylint: disable=cyclic-import + from opencodeblocks.graphics.view import ( + OCBView, + ) # pylint: disable=cyclic-import + from opencodeblocks.scene.scene import OCBScene # pylint: disable=cyclic-import self.layout = QVBoxLayout(self.root) self.layout.setContentsMargins( self.edge_size * 2, self.title_widget.height() + self.edge_size * 2, self.edge_size * 2, - self.edge_size * 2 + self.edge_size * 2, ) self.child_scene = OCBScene() diff --git a/opencodeblocks/blocks/sliderblock.py b/opencodeblocks/blocks/sliderblock.py index b04bbae4..0fa580b2 100644 --- a/opencodeblocks/blocks/sliderblock.py +++ b/opencodeblocks/blocks/sliderblock.py @@ -9,6 +9,7 @@ from PyQt5.QtWidgets import QHBoxLayout, QLabel, QLineEdit, QSlider, QVBoxLayout from opencodeblocks.blocks.executableblock import OCBExecutableBlock + class OCBSliderBlock(OCBExecutableBlock): """ Features a slider ranging from 0 to 1 and an area to choose what value to assign the slider to. @@ -44,7 +45,7 @@ def __init__(self, **kwargs): self.holder.setWidget(self.root) def valueChanged(self): - """ This is called when the value of the slider changes """ + """This is called when the value of the slider changes""" self.variable_value.setText(f"{self.value}") # Make sure that the slider is initialized before trying to run it. if self.scene() is not None: @@ -52,9 +53,10 @@ def valueChanged(self): @property def source(self): - """ The "source code" of the slider i.e an assignement to the value of the slider """ + """The "source code" of the slider i.e an assignement to the value of the slider""" python_code = f"{self.var_name} = {self.value}" return python_code + @source.setter def source(self, value: str): raise RuntimeError("The source of a sliderblock is read-only.") diff --git a/opencodeblocks/graphics/widget.py b/opencodeblocks/graphics/widget.py index 0836b437..d5025355 100644 --- a/opencodeblocks/graphics/widget.py +++ b/opencodeblocks/graphics/widget.py @@ -62,7 +62,7 @@ def save(self): self.scene.save(self.savepath) def saveAsJupyter(self): - """ Save the current graph notebook as a regular python notebook """ + """Save the current graph notebook as a regular python notebook""" self.scene.save_to_ipynb(self.savepath) def load(self, filepath: str): diff --git a/opencodeblocks/graphics/worker.py b/opencodeblocks/graphics/worker.py index 5689385c..ea339952 100644 --- a/opencodeblocks/graphics/worker.py +++ b/opencodeblocks/graphics/worker.py @@ -42,7 +42,7 @@ async def run_code(self): self.signals.stdout.emit(output) elif output_type == "image": self.signals.image.emit(output) - elif output_type == 'error': + elif output_type == "error": self.signals.error.emit() self.signals.stdout.emit(output) self.signals.finished.emit() diff --git a/tests/integration/blocks/test_block.py b/tests/integration/blocks/test_block.py index 1b843b11..93d42bce 100644 --- a/tests/integration/blocks/test_block.py +++ b/tests/integration/blocks/test_block.py @@ -73,6 +73,6 @@ def testing_drag(msgQueue: CheckingQueue): msgQueue.stop() apply_function_inapp(self.window, testing_drag) - + def test_finish(self): - self.window.close() \ No newline at end of file + self.window.close() diff --git a/tests/integration/blocks/test_codeblock.py b/tests/integration/blocks/test_codeblock.py index b902cd6d..a6f79a7d 100644 --- a/tests/integration/blocks/test_codeblock.py +++ b/tests/integration/blocks/test_codeblock.py @@ -17,15 +17,14 @@ from tests.integration.utils import apply_function_inapp, CheckingQueue, start_app -class TestCodeBlocks(): - +class TestCodeBlocks: @pytest.fixture(autouse=True) def setup(self): - """ Setup reused variables. """ + """Setup reused variables.""" start_app(self) def test_run_python(self): - """ run source code when run button is pressed. """ + """run source code when run button is pressed.""" # Add a block with the source to the window EXPRESSION = "3 + 5 * 2" @@ -58,42 +57,46 @@ def testing_run(msgQueue: CheckingQueue): msgQueue.stop() apply_function_inapp(self.window, testing_run) - def test_run_block_with_path(self): - """ runs blocks with the correct working directory for the kernel """ + """runs blocks with the correct working directory for the kernel""" file_example_path = "./tests/assets/example_graph1.ipyg" asset_path = "./tests/assets/data.txt" self.ocb_widget.scene.load(os.path.abspath(file_example_path)) - + def testing_path(msgQueue: CheckingQueue): block_of_test: OCBCodeBlock = None for item in self.ocb_widget.scene.items(): - if isinstance(item,OCBCodeBlock) and item.title == "test1": + if isinstance(item, OCBCodeBlock) and item.title == "test1": block_of_test = item - print(item.title) break - msgQueue.check_equal(block_of_test is not None, True, "example_graph1 contains a block titled test1") + msgQueue.check_equal( + block_of_test is not None, + True, + "example_graph1 contains a block titled test1", + ) def run_block(): block_of_test.run_code() msgQueue.run_lambda(run_block) - time.sleep(0.1) # wait for the lambda to complete. + time.sleep(0.1) # wait for the lambda to complete. while block_of_test.is_running: - time.sleep(0.1) # wait for the execution to finish. + time.sleep(0.1) # wait for the execution to finish. + + time.sleep(0.1) file_content = open(asset_path).read() - print('"'+block_of_test.stdout.strip()+'"') - print('"'+file_content+'"') - - msgQueue.check_equal(block_of_test.stdout.strip(), file_content, "The asset file is read properly") + msgQueue.check_equal( + block_of_test.stdout.strip(), + file_content, + "The asset file is read properly", + ) msgQueue.stop() apply_function_inapp(self.window, testing_path) - def test_finish(self): - self.window.close() \ No newline at end of file + self.window.close() diff --git a/tests/integration/blocks/test_flow.py b/tests/integration/blocks/test_flow.py index 3121567c..2f041aa3 100644 --- a/tests/integration/blocks/test_flow.py +++ b/tests/integration/blocks/test_flow.py @@ -14,24 +14,28 @@ class TestCodeBlocks: - @pytest.fixture(autouse=True) def setup(self): - """ Setup reused variables. """ + """Setup reused variables.""" start_app(self) self.ocb_widget.scene.load("tests/assets/flow_test.ipyg") - self.titles = ["Test flow 5", "Test flow 4", "Test no connection 1", - "Test input only 2", "Test output only 1"] - self.blocks_to_run = [None]*5 + self.titles = [ + "Test flow 5", + "Test flow 4", + "Test no connection 1", + "Test input only 2", + "Test output only 1", + ] + self.blocks_to_run = [None] * 5 for item in self.ocb_widget.scene.items(): if isinstance(item, OCBCodeBlock): if item.title in self.titles: self.blocks_to_run[self.titles.index(item.title)] = item def test_duplicated_run(self): - """ Don't run a block twice when the execution flows """ + """Don't run a block twice when the execution flows""" for b in self.blocks_to_run: b.stdout = "" @@ -45,7 +49,7 @@ def run_block(): msgQueue.run_lambda(run_block) time.sleep(0.1) while block_to_run.is_running: - time.sleep(0.1) # wait for the execution to finish. + time.sleep(0.1) # wait for the execution to finish. # 6 and not 6\n6 msgQueue.check_equal(block_to_run.stdout.strip(), "6") @@ -54,8 +58,8 @@ def run_block(): apply_function_inapp(self.window, testing_no_duplicates) def test_flow_left(self): - """ Correct flow when pressing left run """ - + """Correct flow when pressing left run""" + for b in self.blocks_to_run: b.stdout = "" @@ -70,7 +74,7 @@ def run_block(): msgQueue.run_lambda(run_block) time.sleep(0.1) while block_to_run.is_running: - time.sleep(0.1) # wait for the execution to finish. + time.sleep(0.1) # wait for the execution to finish. msgQueue.check_equal(block_to_run.stdout.strip(), "6") msgQueue.check_equal(block_to_not_run.stdout.strip(), "") @@ -126,4 +130,4 @@ def run_block(): apply_function_inapp(self.window, testing_run) def test_finish(self): - self.window.close() \ No newline at end of file + self.window.close() diff --git a/tests/integration/test_window.py b/tests/integration/test_window.py index 780f041c..06a084bd 100644 --- a/tests/integration/test_window.py +++ b/tests/integration/test_window.py @@ -28,4 +28,4 @@ def test_open_file(self, qtbot): def test_window_close(self, qtbot): """closes""" - self.window.close() \ No newline at end of file + self.window.close() diff --git a/tests/integration/utils.py b/tests/integration/utils.py index 89f41067..53a5ab1c 100644 --- a/tests/integration/utils.py +++ b/tests/integration/utils.py @@ -35,35 +35,38 @@ def run_lambda(self, func: Callable, *args, **kwargs): def stop(self): self.put([STOP_MSG]) + class ExceptionForwardingThread(threading.Thread): - """ A Thread class that forwards the exceptions to the calling thread """ + """A Thread class that forwards the exceptions to the calling thread""" + def __init__(self, *args, **kwargs): - """ Create an exception forwarding thread """ + """Create an exception forwarding thread""" super().__init__(*args, **kwargs) self.e = None def run(self): - """ Code ran in another thread """ + """Code ran in another thread""" try: super().run() except Exception as e: self.e = e def join(self): - """ Used to sync the thread with the caller """ + """Used to sync the thread with the caller""" super().join() - print("except: ",self.e) + print("except: ", self.e) if self.e != None: raise self.e def start_app(obj): - """ Create a new app for testing """ + """Create a new app for testing""" obj.window = OCBWindow() obj.ocb_widget = OCBWidget() obj.subwindow = obj.window.mdiArea.addSubWindow(obj.ocb_widget) obj.subwindow.show() + def apply_function_inapp(window: OCBWindow, run_func: Callable): if os.name == "nt": # If on windows @@ -78,7 +81,7 @@ def apply_function_inapp(window: OCBWindow, run_func: Callable): deadCounter = 0 while not stop: - time.sleep(1 / 30) # 30 fps + time.sleep(1 / 30) # 30 fps QApplication.processEvents() if not msgQueue.empty(): msg = msgQueue.get() @@ -93,6 +96,8 @@ def apply_function_inapp(window: OCBWindow, run_func: Callable): deadCounter += 1 if deadCounter >= 3: # Test failed, close was not called - warnings.warn("Warning: you need to call CheckingQueue.stop() at the end of your test !") + warnings.warn( + "Warning: you need to call CheckingQueue.stop() at the end of your test !" + ) break t.join() diff --git a/tests/unit/scene/test_ipynb_conversion.py b/tests/unit/scene/test_ipynb_conversion.py index d73f357d..82c00b43 100644 --- a/tests/unit/scene/test_ipynb_conversion.py +++ b/tests/unit/scene/test_ipynb_conversion.py @@ -51,7 +51,7 @@ def test_is_title(self, mocker: MockerFixture): def real_notebook_conversion_is_coherent(file_path: str): """Checks that the conversion of the ipynb notebook gives a coherent result. - + Args: file_path: the path to a .ipynb file """ @@ -59,7 +59,8 @@ def real_notebook_conversion_is_coherent(file_path: str): ipyg_data = ipynb_to_ipyg(ipynb_data) check_conversion_coherence(ipynb_data, ipyg_data) -def check_conversion_coherence(ipynb_data: OrderedDict, ipyg_data:OrderedDict): + +def check_conversion_coherence(ipynb_data: OrderedDict, ipyg_data: OrderedDict): """Checks that the ipyg data is coherent with the ipynb data. The conversion from ipynb to ipyg should return From 28175847232d628566da40db2f0d345c6b08effe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Del=C3=A8gue?= Date: Mon, 20 Dec 2021 16:53:20 +0100 Subject: [PATCH 22/22] =?UTF-8?q?=F0=9F=AA=B2=20Prevent=20ipynb=20conversi?= =?UTF-8?q?on=20unit=20tests=20from=20interacting=20with=20the=20font=20ma?= =?UTF-8?q?nager.=20These=20are=20not=20integration=20tests=20!=20There=20?= =?UTF-8?q?is=20no=20QApplication=20setup=20that=20allows=20you=20to=20ret?= =?UTF-8?q?rieve=20theme=20information.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- opencodeblocks/scene/from_ipynb_conversion.py | 44 ++++++++++++++----- tests/unit/scene/test_ipynb_conversion.py | 4 +- 2 files changed, 34 insertions(+), 14 deletions(-) diff --git a/opencodeblocks/scene/from_ipynb_conversion.py b/opencodeblocks/scene/from_ipynb_conversion.py index 7c2ba094..3165d690 100644 --- a/opencodeblocks/scene/from_ipynb_conversion.py +++ b/opencodeblocks/scene/from_ipynb_conversion.py @@ -9,10 +9,14 @@ from opencodeblocks.graphics.pyeditor import POINT_SIZE -def ipynb_to_ipyg(data: OrderedDict) -> OrderedDict: - """Convert ipynb data (ipynb file, as ordered dict) into ipyg data (ipyg, as ordered dict)""" +def ipynb_to_ipyg(data: OrderedDict, use_theme_font: bool = True) -> OrderedDict: + """ + Convert ipynb data (ipynb file, as ordered dict) into ipyg data (ipyg, as ordered dict) + - use_theme_font: should the height of the blocks be computed based on the current + font selected. + """ - blocks_data: List[OrderedDict] = get_blocks_data(data) + blocks_data: List[OrderedDict] = get_blocks_data(data, use_theme_font) edges_data: List[OrderedDict] = get_edges_data(blocks_data) return { @@ -21,7 +25,9 @@ def ipynb_to_ipyg(data: OrderedDict) -> OrderedDict: } -def get_blocks_data(data: OrderedDict) -> List[OrderedDict]: +def get_blocks_data( + data: OrderedDict, use_theme_font: bool = True +) -> List[OrderedDict]: """ Get the blocks corresponding to a ipynb file, Returns them in the ipyg ordered dict format @@ -31,12 +37,14 @@ def get_blocks_data(data: OrderedDict) -> List[OrderedDict]: return [] # Get the font metrics to determine the size fo the blocks - font = QFont() - font.setFamily(theme_manager().recommended_font_family) - font.setFixedPitch(True) - font.setPointSize(POINT_SIZE) - fontmetrics = QFontMetrics(font) - + fontmetrics = None + if use_theme_font: + font = QFont() + font.setFamily(theme_manager().recommended_font_family) + font.setFixedPitch(True) + font.setPointSize(POINT_SIZE) + fontmetrics = QFontMetrics(font) + blocks_data: List[OrderedDict] = [] next_block_x_pos: float = 0 @@ -50,15 +58,27 @@ def get_blocks_data(data: OrderedDict) -> List[OrderedDict]: block_type: str = cell["cell_type"] text: str = cell["source"] + + boundingWidth = 10 + if use_theme_font: + boundingWidth = fontmetrics.boundingRect(line).width() text_width: float = ( - max(fontmetrics.boundingRect(line).width() for line in text) + max(boundingWidth for line in text) if len(text) > 0 else 0 ) block_width: float = max(text_width + MARGIN_X, BLOCK_MIN_WIDTH) + + lineSpacing = 2 + lineWidth = 10 + + if use_theme_font: + lineSpacing = fontmetrics.lineSpacing() + lineWidth = fontmetrics.lineWidth() + text_height: float = len(text) * ( - fontmetrics.lineSpacing() + fontmetrics.lineWidth() + lineSpacing + lineWidth ) block_height: float = text_height + MARGIN_Y diff --git a/tests/unit/scene/test_ipynb_conversion.py b/tests/unit/scene/test_ipynb_conversion.py index 82c00b43..88d1a4cf 100644 --- a/tests/unit/scene/test_ipynb_conversion.py +++ b/tests/unit/scene/test_ipynb_conversion.py @@ -15,7 +15,7 @@ class TestIpynbConversion: def test_empty_data(self, mocker: MockerFixture): """should return empty ipyg graph for empty data.""" - check.equal(ipynb_to_ipyg({}), {"blocks": [], "edges": []}) + check.equal(ipynb_to_ipyg({}, False), {"blocks": [], "edges": []}) def test_empty_notebook_data(self, mocker: MockerFixture): """should return expected graph for a real empty notebook data.""" @@ -56,7 +56,7 @@ def real_notebook_conversion_is_coherent(file_path: str): file_path: the path to a .ipynb file """ ipynb_data = load_json(file_path) - ipyg_data = ipynb_to_ipyg(ipynb_data) + ipyg_data = ipynb_to_ipyg(ipynb_data, False) check_conversion_coherence(ipynb_data, ipyg_data)