Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

🛠️Execution Refactor & 🎉 Nested Nodes #113

Merged
merged 27 commits into from
Dec 20, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
ee02258
🔨 Add the concept of an executable block as not all block can be exec…
vanyle Dec 11, 2021
25944e9
☔ Correction for test and fix for #112
vanyle Dec 11, 2021
a9386ad
🎉 Slider are executable and allow for real time displays !
vanyle Dec 11, 2021
e90d221
📝 Add an example for sliders with a linear classifier.
vanyle Dec 11, 2021
27da4c1
🎉 Make drawing block executable
vanyle Dec 11, 2021
a19cb24
✨ Black formatting
vanyle Dec 11, 2021
e9c2615
🪲 Make kernel an attribute of scene so that when multiple files are o…
vanyle Dec 11, 2021
77ec2fe
Merge remote-tracking branch 'origin/test/flow' into feature/nested_node
vanyle Dec 11, 2021
48ac3e8
✨ Fixes to pass the CI
vanyle Dec 11, 2021
17fef8a
✨ Refactoring tests to pass CI ...
vanyle Dec 11, 2021
2e4b402
🪲 Fix for #94 as well as *ExecutingBlock* serialization issues.
vanyle Dec 11, 2021
58df0ce
🎉 Work on implementing the container. A circular dependency needs to …
vanyle Dec 11, 2021
b1762ce
Merge remote-tracking branch 'origin/Add-ipynb-conversion-draft' into…
vanyle Dec 12, 2021
4a58079
🎉 The container node works !
vanyle Dec 12, 2021
c3707b5
✨ Useless changes to make the CI happy (that needlessly steal the tim…
vanyle Dec 12, 2021
99ffc0c
🪲 Change CWD instead of path.
vanyle Dec 12, 2021
40de428
Merge branch 'master' into feature/nested_node
vanyle Dec 12, 2021
f2f7ce3
✨ Remove trailing whitespace
vanyle Dec 12, 2021
3f83c58
:memo: Update linear_classifier
MathisFederico Dec 12, 2021
7c40d1a
✨ Add @abstract decorator to abstract method.
vanyle Dec 12, 2021
9907f11
☔ Add tests for the CWD bug. Make test quicker but waiting properly f…
vanyle Dec 12, 2021
e222912
Merge remote-tracking branch 'origin/feature/nested_node' into featur…
vanyle Dec 12, 2021
b849327
☔ Add test to prevent #112 regressions.
vanyle Dec 12, 2021
6649b32
Merge branch 'master' into feature/nested_node
vanyle Dec 20, 2021
76c5786
☔🪲 Change properly catch error in tests. Remove code that was wrongfu…
vanyle Dec 20, 2021
df51985
✨ Black formatting ☔ Test reliability improvements
vanyle Dec 20, 2021
2817584
🪲 Prevent ipynb conversion unit tests from interacting with the font …
vanyle Dec 20, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions blocks/container.ocbb
Original file line number Diff line number Diff line change
@@ -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
}
}
}
541 changes: 541 additions & 0 deletions examples/linear_classifier.ipyg

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion opencodeblocks/blocks/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
""" Module for the OCB Blocks of different types. """

from opencodeblocks.blocks.sliderblock import OCBSliderBlock
from opencodeblocks.blocks.block import OCBBlock
from opencodeblocks.blocks.codeblock import OCBCodeBlock
from opencodeblocks.blocks.markdownblock import OCBMarkdownBlock
from opencodeblocks.blocks.drawingblock import OCBDrawingBlock
from opencodeblocks.blocks.containerblock import OCBContainerBlock
4 changes: 0 additions & 4 deletions opencodeblocks/blocks/block.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,6 @@ class OCBBlock(QGraphicsItem, Serializable):
def __init__(
self,
block_type: str = "base",
source: str = "",
position: tuple = (0, 0),
width: int = DEFAULT_DATA["width"],
height: int = DEFAULT_DATA["height"],
Expand All @@ -57,7 +56,6 @@ def __init__(

Args:
block_type: Block type.
source: Block source text.
position: Block position in the scene.
width: Block width.
height: Block height.
Expand All @@ -70,8 +68,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 = []
Expand Down
148 changes: 34 additions & 114 deletions opencodeblocks/blocks/codeblock.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,20 @@

""" 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.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
Expand All @@ -28,38 +28,32 @@ class OCBCodeBlock(OCBBlock):

"""

DEFAULT_DATA = {
**OCBBlock.DEFAULT_DATA,
"source": "",
}
MANDATORY_FIELDS = OCBBlock.MANDATORY_FIELDS
def __init__(self, source: str = "", **kwargs):

def __init__(self, **kwargs):
"""
Create a new OCBCodeBlock.
Initialize all the child widgets specific to this block type
"""
DEFAULT_DATA = {
**OCBBlock.DEFAULT_DATA,
"source": "",
}
MANDATORY_FIELDS = OCBBlock.MANDATORY_FIELDS

super().__init__(**kwargs)
self.source_editor = PythonEditor(self)

self._source = ""
self._stdout = ""

super().__init__(**kwargs)
self.source = source

self.output_panel_height = self.height / 3
self._min_output_panel_height = 20
self._min_source_editor_height = 20

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()
Expand Down Expand Up @@ -89,18 +83,32 @@ def init_run_button(self):
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.clicked.connect(self.run_left)
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.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
Expand All @@ -110,102 +118,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(
Expand Down
38 changes: 38 additions & 0 deletions opencodeblocks/blocks/containerblock.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
"""
Exports OCBContainerBlock.
"""

from PyQt5.QtWidgets import QVBoxLayout
from opencodeblocks.blocks.block import OCBBlock


class OCBContainerBlock(OCBBlock):
"""
A block that can contain other blocks.
"""

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.child_scene = OCBScene()
self.child_view = OCBView(self.child_scene)
self.layout.addWidget(self.child_view)

self.holder.setWidget(self.root)
Loading