diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index d1379f8c..d8e8f557 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -1,9 +1,9 @@
-# OpenCodeBlocks - Contributing guide
+# PyFlow - Contributing guide
-[![Pytest badge](https://github.com/MathisFederico/OpenCodeBlocks/actions/workflows/python-tests.yml/badge.svg?branch=master)](https://github.com/MathisFederico/OpenCodeBlocks/actions/workflows/python-tests.yml) [![Pylint badge](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FMathisFederico%2F00ce73155619a4544884ca6d251954b3%2Fraw%2Fopencodeblocks_pylint_badge.json)](https://github.com/MathisFederico/OpenCodeBlocks/actions/workflows/python-pylint.yml) [![Unit coverage badge](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FMathisFederico%2F00ce73155619a4544884ca6d251954b3%2Fraw%2Fopencodeblocks_unit_coverage_badge.json)](https://github.com/MathisFederico/OpenCodeBlocks/actions/workflows/python-coverage.yml) [![Integration coverage badge](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FMathisFederico%2F00ce73155619a4544884ca6d251954b3%2Fraw%2Fopencodeblocks_integration_coverage_badge.json)](https://github.com/MathisFederico/OpenCodeBlocks/actions/workflows/python-coverage.yml)
+[![Pytest badge](https://github.com/Bycelium/OpenCodeBlocks/actions/workflows/python-tests.yml/badge.svg?branch=master)](https://github.com/MathisFederico/OpenCodeBlocks/actions/workflows/python-tests.yml) [![Pylint badge](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FMathisFederico%2F00ce73155619a4544884ca6d251954b3%2Fraw%2Fopencodeblocks_pylint_badge.json)](https://github.com/MathisFederico/OpenCodeBlocks/actions/workflows/python-pylint.yml) [![Unit coverage badge](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FMathisFederico%2F00ce73155619a4544884ca6d251954b3%2Fraw%2Fopencodeblocks_unit_coverage_badge.json)](https://github.com/MathisFederico/OpenCodeBlocks/actions/workflows/python-coverage.yml) [![Integration coverage badge](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FMathisFederico%2F00ce73155619a4544884ca6d251954b3%2Fraw%2Fopencodeblocks_integration_coverage_badge.json)](https://github.com/MathisFederico/OpenCodeBlocks/actions/workflows/python-coverage.yml)
-Whenever you encounter a :beetle: **bug** or have :tada: **feature request**,
-report this via [GitHub issues](https://github.com/MathisFederico/OpenCodeBlocks/issues).
+Whenever you encounter a :beetle: **bug** or have :tada: **feature request**,
+report this via [GitHub issues](https://github.com/Bycelium/PyFlow/issues).
We are happy to receive contributions in the form of **pull requests** via GitHub.
@@ -11,10 +11,10 @@ Feel free to fork the repository, implement your changes and create a merge requ
## Getting started
-First, make sure you have `python` installed. You will need to install all the `requirements` and the `requirements-dev` using the following commands:
+First, make sure you have `python` installed. You will need to install all the `requirements` and the `requirements-dev` using the following commands:
-* `pip install -r requirements.txt`
-* `pip install -r requirements-dev.txt`
+* `pip install -r requirements.txt`
+* `pip install -r requirements-dev.txt`
You can run the program with `python main.py`
@@ -24,10 +24,10 @@ Before doing your **pull request**, check using `pylint` and `pytest` that there
pylint .\opencodeblocks\
```
-Some `pylint` issues can be fixed automatically using `autopep8`, with the following command:
+Some `pylint` issues can be fixed automatically using `black`, with the following command:
```bash
-autopep8 --in-place --recursive --aggressive opencodeblocks
+black .
```
```bash
@@ -36,12 +36,13 @@ pytest --cov=opencodeblocks --cov-report=html tests/unit
We want to keep the *Pylint* score above *9.0*.
+The comments and docstrings should preferably follow [these](https://google.github.io/styleguide/pyguide.html#38-comments-and-docstrings) guidelines.
+
## Git Commit Messages
Commits should start with a Capital letter and should be written in present tense (e.g. ``:tada: Add cool new feature`` instead of ``:tada: Added cool new feature``).
You should also start your commit message with one or two applicable emoji. This does not only look great but also makes you rethink what to add to a commit. Make many but small commits!
-
Emoji | Description
-----------------|-------------
:tada: `:tada:` | When you add a cool new feature
@@ -71,4 +72,4 @@ This means, given a version number MAJOR.MINOR.PATCH, we will increment the:
1. MAJOR version when we make incompatible API changes,
2. MINOR version when we add functionality in a backwards compatible manner, and
-3. PATCH version when we make backwards compatible bug fixes.
\ No newline at end of file
+3. PATCH version when we make backwards compatible bug fixes.
diff --git a/README.md b/README.md
index 61301f2a..222e8397 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,4 @@
-# OpenCodeBlocks
+# PyFlow
[![Pytest badge](https://github.com/MathisFederico/OpenCodeBlocks/actions/workflows/python-tests.yml/badge.svg?branch=master)](https://github.com/MathisFederico/OpenCodeBlocks/actions/workflows/python-tests.yml)
[![Codacy Badge](https://app.codacy.com/project/badge/Grade/ddd03302fd7c4849b452959753bc0939)](https://www.codacy.com/gh/MathisFederico/OpenCodeBlocks/dashboard?utm_source=github.com&utm_medium=referral&utm_content=MathisFederico/OpenCodeBlocks&utm_campaign=Badge_Grade)
@@ -9,7 +9,7 @@
[![Licence - GPLv3](https://img.shields.io/github/license/MathisFederico/Crafting?style=plastic)](https://www.gnu.org/licenses/)
[![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](CONTRIBUTING.md)
-OpenCodeBlocks is an open-source tool for modular visual programing in python !
+PyFlow is an open-source tool for modular visual programing in python !
Although for now the tool is in Beta and features are coming in bit by bit, stay tuned for the first release soon !
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/examples/linear_classifier.ipyg b/examples/linear_classifier.ipyg
new file mode 100644
index 00000000..a02f6f4a
--- /dev/null
+++ b/examples/linear_classifier.ipyg
@@ -0,0 +1,541 @@
+{
+ "id": 2034509196736,
+ "blocks": [
+ {
+ "id": 2034638878464,
+ "title": "",
+ "block_type": "OCBMarkdownBlock",
+ "splitter_pos": [
+ 0,
+ 200
+ ],
+ "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,
+ 272
+ ],
+ "position": [
+ 53.875000000000085,
+ 212.25
+ ],
+ "width": 439,
+ "height": 325,
+ "metadata": {
+ "title_metadata": {
+ "color": "white",
+ "font": "Ubuntu",
+ "size": 10
+ }
+ },
+ "sockets": [
+ {
+ "id": 2034686483184,
+ "type": "input",
+ "position": [
+ 0.0,
+ 40.0
+ ],
+ "metadata": {
+ "color": "#FF55FFF0",
+ "linecolor": "#FF000000",
+ "linewidth": 1.0,
+ "radius": 10.0
+ }
+ },
+ {
+ "id": 2034686483328,
+ "type": "output",
+ "position": [
+ 439.0,
+ 40.0
+ ],
+ "metadata": {
+ "color": "#FF55FFF0",
+ "linecolor": "#FF000000",
+ "linewidth": 1.0,
+ "radius": 10.0
+ }
+ }
+ ],
+ "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": [
+ 189,
+ 85
+ ],
+ "position": [
+ -929.7499999999999,
+ -121.31250000000003
+ ],
+ "width": 706,
+ "height": 327,
+ "metadata": {
+ "title_metadata": {
+ "color": "white",
+ "font": "Ubuntu",
+ "size": 10
+ }
+ },
+ "sockets": [
+ {
+ "id": 2034723534592,
+ "type": "input",
+ "position": [
+ 0.0,
+ 40.0
+ ],
+ "metadata": {
+ "color": "#FF55FFF0",
+ "linecolor": "#FF000000",
+ "linewidth": 1.0,
+ "radius": 10.0
+ }
+ },
+ {
+ "id": 2034723534736,
+ "type": "output",
+ "position": [
+ 706.0,
+ 40.0
+ ],
+ "metadata": {
+ "color": "#FF55FFF0",
+ "linecolor": "#FF000000",
+ "linewidth": 1.0,
+ "radius": 10.0
+ }
+ }
+ ],
+ "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,
+ "title": "Slider",
+ "block_type": "OCBSliderBlock",
+ "splitter_pos": [],
+ "position": [
+ -890.625,
+ 258.50000000000006
+ ],
+ "width": 618,
+ "height": 184,
+ "metadata": {
+ "title_metadata": {
+ "color": "white",
+ "font": "Ubuntu",
+ "size": 10
+ }
+ },
+ "sockets": [
+ {
+ "id": 2034723678672,
+ "type": "input",
+ "position": [
+ 0.0,
+ 42.0
+ ],
+ "metadata": {
+ "color": "#FF55FFF0",
+ "linecolor": "#FF000000",
+ "linewidth": 1.0,
+ "radius": 10.0
+ }
+ },
+ {
+ "id": 2034723678816,
+ "type": "output",
+ "position": [
+ 618.0,
+ 42.0
+ ],
+ "metadata": {
+ "color": "#FF55FFF0",
+ "linecolor": "#FF000000",
+ "linewidth": 1.0,
+ "radius": 10.0
+ }
+ }
+ ],
+ "value": "0.82",
+ "var_name": "b"
+ },
+ {
+ "id": 2034723714816,
+ "title": "Slider",
+ "block_type": "OCBSliderBlock",
+ "splitter_pos": [],
+ "position": [
+ -901.1874999999999,
+ 471.8750000000001
+ ],
+ "width": 618,
+ "height": 184,
+ "metadata": {
+ "title_metadata": {
+ "color": "white",
+ "font": "Ubuntu",
+ "size": 10
+ }
+ },
+ "sockets": [
+ {
+ "id": 2034723715680,
+ "type": "input",
+ "position": [
+ 0.0,
+ 42.0
+ ],
+ "metadata": {
+ "color": "#FF55FFF0",
+ "linecolor": "#FF000000",
+ "linewidth": 1.0,
+ "radius": 10.0
+ }
+ },
+ {
+ "id": 2034723715824,
+ "type": "output",
+ "position": [
+ 618.0,
+ 42.0
+ ],
+ "metadata": {
+ "color": "#FF55FFF0",
+ "linecolor": "#FF000000",
+ "linewidth": 1.0,
+ "radius": 10.0
+ }
+ }
+ ],
+ "value": "0.41",
+ "var_name": "m"
+ },
+ {
+ "id": 2034879162976,
+ "title": "Regression",
+ "block_type": "OCBCodeBlock",
+ "splitter_pos": [
+ 0,
+ 276
+ ],
+ "position": [
+ 782.4375000000001,
+ -676.0000000000001
+ ],
+ "width": 434,
+ "height": 329,
+ "metadata": {
+ "title_metadata": {
+ "color": "white",
+ "font": "Ubuntu",
+ "size": 10
+ }
+ },
+ "sockets": [
+ {
+ "id": 2034879163840,
+ "type": "input",
+ "position": [
+ 0.0,
+ 40.0
+ ],
+ "metadata": {
+ "color": "#FF55FFF0",
+ "linecolor": "#FF000000",
+ "linewidth": 1.0,
+ "radius": 10.0
+ }
+ },
+ {
+ "id": 2034879163984,
+ "type": "output",
+ "position": [
+ 434.0,
+ 40.0
+ ],
+ "metadata": {
+ "color": "#FF55FFF0",
+ "linecolor": "#FF000000",
+ "linewidth": 1.0,
+ "radius": 10.0
+ }
+ }
+ ],
+ "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,
+ "title": "Show user input",
+ "block_type": "OCBCodeBlock",
+ "splitter_pos": [
+ 0,
+ 220
+ ],
+ "position": [
+ 611.6875000000001,
+ 251.81250000000006
+ ],
+ "width": 685,
+ "height": 273,
+ "metadata": {
+ "title_metadata": {
+ "color": "white",
+ "font": "Ubuntu",
+ "size": 10
+ }
+ },
+ "sockets": [
+ {
+ "id": 2034879287152,
+ "type": "input",
+ "position": [
+ 0.0,
+ 40.0
+ ],
+ "metadata": {
+ "color": "#FF55FFF0",
+ "linecolor": "#FF000000",
+ "linewidth": 1.0,
+ "radius": 10.0
+ }
+ },
+ {
+ "id": 2034879197248,
+ "type": "output",
+ "position": [
+ 685.0,
+ 40.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 * 3.9000000000000004 + 1.64\nAccuracy:\n0.9724161281048267\n"
+ },
+ {
+ "id": 2034886210608,
+ "title": "Create a new linear model",
+ "block_type": "OCBCodeBlock",
+ "splitter_pos": [
+ 90,
+ 85
+ ],
+ "position": [
+ -160.3125,
+ -374.50000000000006
+ ],
+ "width": 840,
+ "height": 228,
+ "metadata": {
+ "title_metadata": {
+ "color": "white",
+ "font": "Ubuntu",
+ "size": 10
+ }
+ },
+ "sockets": [
+ {
+ "id": 2034886211472,
+ "type": "input",
+ "position": [
+ 0.0,
+ 40.0
+ ],
+ "metadata": {
+ "color": "#FF55FFF0",
+ "linecolor": "#FF000000",
+ "linewidth": 1.0,
+ "radius": 10.0
+ }
+ },
+ {
+ "id": 2034886211616,
+ "type": "output",
+ "position": [
+ 840.0,
+ 40.0
+ ],
+ "metadata": {
+ "color": "#FF55FFF0",
+ "linecolor": "#FF000000",
+ "linewidth": 1.0,
+ "radius": 10.0
+ }
+ }
+ ],
+ "source": "from sklearn import linear_model\r\nreg = 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,
+ 278
+ ],
+ "position": [
+ 767.1875000000002,
+ -279.9375
+ ],
+ "width": 816,
+ "height": 331,
+ "metadata": {
+ "title_metadata": {
+ "color": "white",
+ "font": "Ubuntu",
+ "size": 10
+ }
+ },
+ "sockets": [
+ {
+ "id": 2136886540752,
+ "type": "input",
+ "position": [
+ 0.0,
+ 40.0
+ ],
+ "metadata": {
+ "color": "#FF55FFF0",
+ "linecolor": "#FF000000",
+ "linewidth": 1.0,
+ "radius": 10.0
+ }
+ },
+ {
+ "id": 2136886540896,
+ "type": "output",
+ "position": [
+ 816.0,
+ 40.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": 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/__init__.py b/opencodeblocks/__init__.py
index 1de73c48..8b299e47 100644
--- a/opencodeblocks/__init__.py
+++ b/opencodeblocks/__init__.py
@@ -4,6 +4,6 @@
""" OpenCodeBlocks: An open-source tool for modular visual programing in python """
-__appname__ = 'OpenCodeBlocks'
-__author__ = 'Mathïs Fédérico'
-__version__ = '0.0.1'
+__appname__ = "OpenCodeBlocks"
+__author__ = "Mathïs Fédérico"
+__version__ = "0.0.1"
diff --git a/opencodeblocks/blocks/__init__.py b/opencodeblocks/blocks/__init__.py
index 82fed7bc..3a2eca69 100644
--- a/opencodeblocks/blocks/__init__.py
+++ b/opencodeblocks/blocks/__init__.py
@@ -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
diff --git a/opencodeblocks/blocks/block.py b/opencodeblocks/blocks/block.py
index 29763b57..9aafddf7 100644
--- a/opencodeblocks/blocks/block.py
+++ b/opencodeblocks/blocks/block.py
@@ -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"],
@@ -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.
@@ -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 = []
@@ -162,8 +158,7 @@ def get_socket_pos(self, socket: OCBSocket) -> Tuple[float]:
y = y_offset
else:
side_lenght = self.height - y_offset - 2 * socket.radius - self.edge_size
- y = y_offset + side_lenght * \
- sockets.index(socket) / (len(sockets) - 1)
+ y = y_offset + side_lenght * sockets.index(socket) / (len(sockets) - 1)
return x, y
def update_sockets(self):
diff --git a/opencodeblocks/blocks/codeblock.py b/opencodeblocks/blocks/codeblock.py
index f2dad9a0..229f5681 100644
--- a/opencodeblocks/blocks/codeblock.py
+++ b/opencodeblocks/blocks/codeblock.py
@@ -3,20 +3,29 @@
""" Module for the base OCB Code Block. """
-from typing import List, OrderedDict
-from PyQt5.QtWidgets import QPushButton, QTextEdit
+from typing import OrderedDict, Optional
+from PyQt5.QtWidgets import (
+ QApplication,
+ QPushButton,
+ QTextEdit,
+ QWidget,
+ QStyleOptionGraphicsItem,
+)
+from PyQt5.QtCore import QTimer, Qt
+from PyQt5.QtGui import QPen, QColor, QPainter, QPainterPath
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
@@ -28,21 +37,25 @@ 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
@@ -52,14 +65,16 @@ def __init__(self, **kwargs):
self._splitter_size = [1, 1]
self._cached_stdout = ""
self.has_been_run = False
+ self.blocks_to_run = []
- # 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)
+ self._pen_outline = QPen(QColor("#7F000000"))
+ self._pen_outline_running = QPen(QColor("#FF0000"))
+ self._pen_outline_transmitting = QPen(QColor("#00ff00"))
+ self._pen_outlines = [
+ self._pen_outline,
+ self._pen_outline_running,
+ self._pen_outline_transmitting,
+ ]
# Add output pannel
self.output_panel = self.init_output_panel()
@@ -88,23 +103,36 @@ 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.clicked.connect(self.run_left)
+ 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.clicked.connect(self.run_right)
+ 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"""
+ if self.run_color != 0:
+ self._interrupt_execution()
+ else:
+ self.run_right()
+
+ def handle_run_left(self):
+ """Called when the button for "Run Left" was pressed"""
+ if self.run_color != 0:
+ self._interrupt_execution()
+ else:
+ self.run_left()
+
def run_code(self):
"""Run the code in the block"""
+
# Reset stdout
self._cached_stdout = ""
@@ -112,102 +140,7 @@ 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
-
- def reset_buttons(self):
- """Reset the text of the run buttons"""
- 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
+ super().run_code() # actually run the code
def update_title(self):
"""Change the geometry of the title widget"""
@@ -239,6 +172,35 @@ def update_all(self):
self.update_output_panel()
self.update_run_all_button()
+ def paint(
+ self,
+ painter: QPainter,
+ option: QStyleOptionGraphicsItem,
+ widget: Optional[QWidget] = None,
+ ):
+ """Paint the code block"""
+ path_content = QPainterPath()
+ path_content.setFillRule(Qt.FillRule.WindingFill)
+ path_content.addRoundedRect(
+ 0, 0, self.width, self.height, self.edge_size, self.edge_size
+ )
+ painter.setPen(Qt.PenStyle.NoPen)
+ painter.setBrush(self._brush_background)
+ painter.drawPath(path_content.simplified())
+
+ # outline
+ path_outline = QPainterPath()
+ path_outline.addRoundedRect(
+ 0, 0, self.width, self.height, self.edge_size, self.edge_size
+ )
+ painter.setPen(
+ self._pen_outline_selected
+ if self.isSelected()
+ else self._pen_outlines[self.run_color]
+ )
+ painter.setBrush(Qt.BrushStyle.NoBrush)
+ painter.drawPath(path_outline.simplified())
+
@property
def source(self) -> str:
"""Source code"""
@@ -251,6 +213,17 @@ def source(self, value: str):
self.source_editor.setText(value)
self._source = value
+ @property
+ def run_color(self) -> int:
+ """Run color"""
+ return self._run_color
+
+ @run_color.setter
+ def run_color(self, value: int):
+ self._run_color = value
+ # Update to force repaint
+ self.update()
+
@property
def stdout(self) -> str:
"""Access the content of the output panel of the block"""
@@ -314,6 +287,7 @@ def handle_image(self, image: str):
self.stdout = "" + image
def serialize(self):
+ """Serialize the code block"""
base_dict = super().serialize()
base_dict["source"] = self.source
base_dict["stdout"] = self.stdout
diff --git a/opencodeblocks/blocks/containerblock.py b/opencodeblocks/blocks/containerblock.py
new file mode 100644
index 00000000..df00a483
--- /dev/null
+++ b/opencodeblocks/blocks/containerblock.py
@@ -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)
diff --git a/opencodeblocks/blocks/drawingblock.py b/opencodeblocks/blocks/drawingblock.py
index f45cfc62..e4ec7a46 100644
--- a/opencodeblocks/blocks/drawingblock.py
+++ b/opencodeblocks/blocks/drawingblock.py
@@ -4,20 +4,22 @@
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 """
+ """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
@@ -27,17 +29,17 @@ 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 """
+ """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 """
+ """Draw the content of the widget"""
painter = QPainter(self)
for i in range(self.pixel_width):
@@ -50,48 +52,56 @@ def paintEvent(self, evt: QPaintEvent):
h * j,
w + eps,
h + eps,
+ # hex color encoded as AARRGGBB
QColor.fromRgb(
- self.color_buffer[i][j]))
+ 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)
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 """
+ """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(OCBBlock):
- """ An OCBBlock on which you can draw, to test your CNNs for example"""
+class OCBDrawingBlock(OCBExecutableBlock):
+
+ """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)
+ 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)
- 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
@@ -99,16 +109,33 @@ 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 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 valueChanged(self):
+ """Called when the content of the drawing block changes."""
+ # 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):
+ """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"]:
if dataname in data:
setattr(self, dataname, data[dataname])
diff --git a/opencodeblocks/blocks/executableblock.py b/opencodeblocks/blocks/executableblock.py
new file mode 100644
index 00000000..a6adb77e
--- /dev/null
+++ b/opencodeblocks/blocks/executableblock.py
@@ -0,0 +1,334 @@
+""" Module for the executable block class """
+
+from typing import OrderedDict
+from abc import abstractmethod
+from PyQt5.QtCore import QTimer
+from PyQt5.QtWidgets import QApplication
+
+from networkx.algorithms.traversal.breadth_first_search import bfs_edges
+
+from opencodeblocks.blocks.block import OCBBlock
+from opencodeblocks.graphics.socket import OCBSocket
+
+
+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
+
+ # 0 for normal, 1 for running, 2 for transmitting
+ self.run_color = 0
+
+ # Each element is a list of blocks/edges to be animated
+ # Running will paint each element one after the other
+ self.transmitting_queue = []
+ # Controls the duration of the visual flow animation
+ self.transmitting_duration = 500
+
+ # 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 = self.scene().kernel
+ kernel.execution_queue.append((self, code))
+
+ if kernel.busy is False:
+ kernel.run_queue()
+ self.has_been_run = True
+
+ def execution_finished(self):
+ """Reset the text of the run buttons"""
+ self.run_color = 0
+ self.run_button.setText(">")
+ self.run_all_button.setText(">>")
+ self.blocks_to_run = []
+
+ def _interrupt_execution(self):
+ """Interrupt an execution, reset the blocks in the queue"""
+ for block, _ in self.scene().kernel.execution_queue:
+ # Reset the blocks that have not been run
+ block.reset_has_been_run()
+ block.execution_finished()
+ # Clear kernel execution queue
+ self.scene().kernel.execution_queue = []
+ # Interrupt the kernel
+ self.scene().kernel.kernel_manager.interrupt_kernel()
+ # Clear local execution queue
+ self.blocks_to_run = []
+
+ def transmitting_animation_in(self):
+ """
+ Animate the visual flow
+ Set color to transmitting and set a timer before switching to normal
+ """
+ for elem in self.transmitting_queue[0]:
+ # Set color to transmitting
+ elem.run_color = 2
+ QApplication.processEvents()
+ QTimer.singleShot(self.transmitting_delay, self.transmitting_animation_out)
+
+ def transmitting_animation_out(self):
+ """
+ Animate the visual flow
+ After the timer, set color to normal and move on with the queue
+ """
+ for elem in self.transmitting_queue[0]:
+ # Reset color only if the block will not be run
+ if hasattr(elem, "has_been_run"):
+ if elem.has_been_run is True:
+ elem.run_color = 0
+ else:
+ elem.run_color = 0
+
+ QApplication.processEvents()
+ self.transmitting_queue.pop(0)
+ if len(self.transmitting_queue) != 0:
+ # If the queue is not empty, move forward in the animation
+ self.transmitting_animation_in()
+ else:
+ # Else, run the blocks in the self.blocks_to_run
+ self.run_blocks()
+
+ def custom_bfs(self, start_node, reverse=False):
+ """
+ Graph traversal in BFS to find the blocks that are connected to the start_node
+
+ Args:
+ start_node (Block): The block to start the traversal from
+ reverse (bool): If True, traverse in the direction of outputs
+
+ Returns:
+ list: Blocks to run in topological order (reversed)
+ list: each element is a list of blocks/edges to animate in order
+ """
+ # Blocks to run in topological order
+ blocks_to_run = []
+ # List of lists of blocks/edges to animate in order
+ to_transmit = [[start_node]]
+
+ to_visit = [start_node]
+ while len(to_visit) != 0:
+ # Remove duplicates
+ to_visit = list(set(to_visit))
+
+ # Gather connected edges
+ edges_to_visit = []
+ for block in to_visit:
+ blocks_to_run.append(block)
+ if not reverse:
+ for input_socket in block.sockets_in:
+ for edge in input_socket.edges:
+ edges_to_visit.append(edge)
+ else:
+ for output_socket in block.sockets_out:
+ for edge in output_socket.edges:
+ edges_to_visit.append(edge)
+ to_transmit.append(edges_to_visit)
+
+ # Gather connected blocks
+ to_visit = []
+ for edge in edges_to_visit:
+ if not reverse:
+ to_visit.append(edge.source_socket.block)
+ else:
+ to_visit.append(edge.destination_socket.block)
+ to_transmit.append(to_visit)
+
+ # Remove start node
+ blocks_to_run.pop(0)
+
+ return blocks_to_run, to_transmit
+
+ def right_traversal(self):
+ """
+ Custom graph traversal utility
+ Returns blocks/edges that will potentially be run by run_right
+ from closest to farthest from self
+
+ Returns:
+ list: each element is a list of blocks/edges to animate in order
+ """
+ # Result
+ to_transmit = [[self]]
+
+ # To check if a block has been visited
+ visited = []
+ # We need to visit the inputs of these blocks
+ to_visit_input = [self]
+ # We need to visit the outputs of these blocks
+ to_visit_output = [self]
+
+ # Next stage to put in to_transmit
+ next_edges = []
+ next_blocks = []
+
+ while len(to_visit_input) != 0 or len(to_visit_output) != 0:
+ for block in to_visit_input.copy():
+ # Check input edges and blocks
+ for input_socket in block.sockets_in:
+ for edge in input_socket.edges:
+ if edge not in visited:
+ next_edges.append(edge)
+ visited.append(edge)
+ input_block = edge.source_socket.block
+ to_visit_input.append(input_block)
+ if input_block not in visited:
+ next_blocks.append(input_block)
+ visited.append(input_block)
+ to_visit_input.remove(block)
+ for block in to_visit_output.copy():
+ # Check output edges and blocks
+ for output_socket in block.sockets_out:
+ for edge in output_socket.edges:
+ if edge not in visited:
+ next_edges.append(edge)
+ visited.append(edge)
+ output_block = edge.destination_socket.block
+ to_visit_input.append(output_block)
+ to_visit_output.append(output_block)
+ if output_block not in visited:
+ next_blocks.append(output_block)
+ visited.append(output_block)
+ to_visit_output.remove(block)
+
+ # Add the next stage to to_transmit
+ to_transmit.append(next_edges)
+ to_transmit.append(next_blocks)
+
+ # Reset next stage
+ next_edges = []
+ next_blocks = []
+
+ return to_transmit
+
+ def run_blocks(self):
+ """Run a list of blocks"""
+ for block in self.blocks_to_run[::-1]:
+ if not block.has_been_run:
+ block.run_code()
+ if not self.has_been_run:
+ self.run_code()
+
+ def run_left(self):
+ """Run all of the block's dependencies and then run the block"""
+
+ # Reset has_been_run to make sure that the self is run again
+ self.has_been_run = False
+
+ # To avoid crashing when spamming the button
+ if len(self.transmitting_queue) != 0:
+ return
+
+ # Gather dependencies
+ blocks_to_run, to_transmit = self.custom_bfs(self)
+ self.blocks_to_run = blocks_to_run
+
+ # Set the transmitting queue
+ self.transmitting_queue = to_transmit
+ # Set delay so that the transmitting animation has fixed total duration
+ self.transmitting_delay = int(
+ self.transmitting_duration / len(self.transmitting_queue)
+ )
+ # Start transmitting animation
+ self.transmitting_animation_in()
+
+ def run_right(self):
+ """Run all of the output blocks and all their dependencies"""
+
+ # To avoid crashing when spamming the button
+ if len(self.transmitting_queue) != 0:
+ return
+
+ # Create transmitting queue
+ self.transmitting_queue = self.right_traversal()
+
+ # Gather outputs
+ blocks_to_run, _ = self.custom_bfs(self, reverse=True)
+ self.blocks_to_run = blocks_to_run
+
+ # For each output found
+ for block in blocks_to_run.copy()[::-1]:
+ # Gather dependencies
+ new_blocks_to_run, _ = self.custom_bfs(block)
+ self.blocks_to_run += new_blocks_to_run
+
+ # Set delay so that the transmitting animation has fixed total duration
+ self.transmitting_delay = int(
+ self.transmitting_duration / len(self.transmitting_queue)
+ )
+ # Start transmitting animation
+ self.transmitting_animation_in()
+
+ def reset_has_been_run(self):
+ """Called when the output is an error"""
+ self.has_been_run = False
+
+ @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")
+
+ def handle_stdout(self, value: str):
+ """Handle the stdout signal"""
+
+ def handle_image(self, image: str):
+ """Handle the image signal"""
+
+ def serialize(self):
+ """Return a serialized version of this block"""
+ return super().serialize()
+
+ def deserialize(
+ self, data: OrderedDict, hashmap: dict = None, restore_id: bool = True
+ ):
+ """Restore a codeblock from it's serialized state"""
+ super().deserialize(data, hashmap, restore_id)
diff --git a/opencodeblocks/blocks/markdownblock.py b/opencodeblocks/blocks/markdownblock.py
index 9b3416d5..7c953b00 100644
--- a/opencodeblocks/blocks/markdownblock.py
+++ b/opencodeblocks/blocks/markdownblock.py
@@ -14,11 +14,11 @@
class OCBMarkdownBlock(OCBBlock):
- """ A block that is able to render markdown text """
+ """A block that is able to render markdown text"""
def __init__(self, **kwargs):
"""
- Create a new OCBMarkdownBlock, a block that renders markdown
+ Create a new OCBMarkdownBlock, a block that renders markdown
"""
super().__init__(**kwargs)
@@ -46,13 +46,14 @@ def __init__(self, **kwargs):
self.rendered_markdown = QWebEngineView()
self.rendered_markdown.page().setBackgroundColor(
- QColor.fromRgba64(0, 0, 0, alpha=0))
+ QColor.fromRgba64(0, 0, 0, alpha=0)
+ )
self.splitter.addWidget(self.rendered_markdown)
self.holder.setWidget(self.root)
def valueChanged(self):
- """ Update markdown rendering when the content of the markdown editor changes """
+ """Update markdown rendering when the content of the markdown editor changes"""
t = self.editor.text()
dark_theme = """
@@ -68,7 +69,7 @@ def valueChanged(self):
@property
def text(self) -> str:
- """ The content of the markdown block """
+ """The content of the markdown block"""
return self.editor.text()
@text.setter
@@ -82,10 +83,11 @@ def serialize(self):
return base_dict
- def deserialize(self, data: OrderedDict,
- hashmap: dict = None, restore_id: bool = True):
- """ Restore a markdown block from it's serialized state """
- for dataname in ['text']:
+ def deserialize(
+ self, data: OrderedDict, hashmap: dict = None, restore_id: bool = True
+ ):
+ """Restore a markdown block from it's serialized state"""
+ for dataname in ["text"]:
if dataname in data:
setattr(self, dataname, data[dataname])
diff --git a/opencodeblocks/blocks/sliderblock.py b/opencodeblocks/blocks/sliderblock.py
index 123034a6..0fa580b2 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
-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.
"""
@@ -21,9 +21,8 @@ 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.root)
+ self.variable_layout = QHBoxLayout()
self.variable_text = QLineEdit("slider_value")
self.variable_value = QLabel(f"{self.slider.value()/100}")
@@ -41,16 +40,26 @@ 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"""
- python_code = f"{self.var_name} = {self.value}"
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:
+ self.run_right()
+
+ @property
+ def source(self):
+ """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
- # The code execution part will be added when the execution flow is merged.
- # We print for now
- print(python_code)
+ @source.setter
+ def source(self, value: str):
+ raise RuntimeError("The source of a sliderblock is read-only.")
@property
def value(self):
diff --git a/opencodeblocks/core/serializable.py b/opencodeblocks/core/serializable.py
index bc714a78..4ed6505d 100644
--- a/opencodeblocks/core/serializable.py
+++ b/opencodeblocks/core/serializable.py
@@ -13,7 +13,6 @@ class Serializable:
MANDATORY_FIELDS: OrderedDict = {}
DEFAULT_DATA: Set[str] = {}
-
def __init__(self):
self.id = id(self)
diff --git a/opencodeblocks/graphics/edge.py b/opencodeblocks/graphics/edge.py
index 4376779e..7b178039 100644
--- a/opencodeblocks/graphics/edge.py
+++ b/opencodeblocks/graphics/edge.py
@@ -9,7 +9,11 @@
from PyQt5.QtCore import QPointF, Qt
from PyQt5.QtGui import QColor, QPainter, QPainterPath, QPen
-from PyQt5.QtWidgets import QGraphicsPathItem, QStyleOptionGraphicsItem, QWidget
+from PyQt5.QtWidgets import (
+ QGraphicsPathItem,
+ QStyleOptionGraphicsItem,
+ QWidget,
+)
from opencodeblocks.core.serializable import Serializable
from opencodeblocks.graphics.socket import OCBSocket
@@ -25,9 +29,11 @@ class OCBEdge(QGraphicsPathItem, Serializable):
def __init__(
self,
edge_width: float = 4.0,
- path_type = DEFAULT_DATA["path_type"],
+ path_type=DEFAULT_DATA["path_type"],
edge_color="#001000",
edge_selected_color="#00ff00",
+ edge_running_color="#FF0000",
+ edge_transmitting_color="#00ff00",
source: QPointF = QPointF(0, 0),
destination: QPointF = QPointF(0, 0),
source_socket: OCBSocket = None,
@@ -59,6 +65,17 @@ def __init__(
self._pen_selected = QPen(QColor(edge_selected_color))
self._pen_selected.setWidthF(edge_width)
+ self._pen_running = QPen(QColor(edge_running_color))
+ self._pen_running.setWidthF(edge_width)
+
+ self._pen_transmitting = QPen(QColor(edge_transmitting_color))
+ self._pen_transmitting.setWidthF(edge_width)
+
+ self.pens = [self._pen, self._pen_running, self._pen_transmitting]
+
+ # 0 for normal, 1 for running, 2 for transmitting
+ self.run_color = 0
+
self.setFlag(QGraphicsPathItem.GraphicsItemFlag.ItemIsSelectable)
self.setZValue(-1)
@@ -104,8 +121,13 @@ def paint(
): # pylint:disable=unused-argument
"""Paint the edge."""
self.update_path()
- pen = self._pen_dragging if self.destination_socket is None else self._pen
- painter.setPen(self._pen_selected if self.isSelected() else pen)
+ if self.isSelected():
+ pen = self._pen_selected
+ elif self.destination_socket is None:
+ pen = self._pen_dragging
+ else:
+ pen = self.pens[self.run_color]
+ painter.setPen(pen)
painter.setBrush(Qt.BrushStyle.NoBrush)
painter.drawPath(self.path())
@@ -237,3 +259,14 @@ def deserialize(self, data: OrderedDict, hashmap: dict = None, restore_id=True):
self.update_path()
except KeyError:
self.remove()
+
+ @property
+ def run_color(self) -> int:
+ """Run color"""
+ return self._run_color
+
+ @run_color.setter
+ def run_color(self, value: int):
+ self._run_color = value
+ # Update to force repaint
+ self.update()
diff --git a/opencodeblocks/graphics/function_parsing.py b/opencodeblocks/graphics/function_parsing.py
index 7c18b9ac..4af206a1 100644
--- a/opencodeblocks/graphics/function_parsing.py
+++ b/opencodeblocks/graphics/function_parsing.py
@@ -1,4 +1,3 @@
-
""" Module for code parsing and code execution """
from typing import List, Tuple
@@ -119,11 +118,11 @@ def execute_function(code: str, *args, **kwargs) -> str:
"""
function_name = get_function_name(code)
- execution_code = f'{function_name}('
+ execution_code = f"{function_name}("
for arg in args:
- execution_code += f'{arg},'
+ execution_code += f"{arg},"
for name, value in kwargs.items():
- execution_code += f'{name}={value},'
+ execution_code += f"{name}={value},"
run_cell(code)
- return run_cell(execution_code + ')')
+ return run_cell(execution_code + ")")
diff --git a/opencodeblocks/graphics/kernel.py b/opencodeblocks/graphics/kernel.py
index c5a1f509..6cea732d 100644
--- a/opencodeblocks/graphics/kernel.py
+++ b/opencodeblocks/graphics/kernel.py
@@ -1,4 +1,3 @@
-
""" Module to create and manage ipython kernels """
import queue
@@ -8,7 +7,7 @@
from opencodeblocks.graphics.worker import Worker
-class Kernel():
+class Kernel:
"""jupyter_client kernel used to execute code and return output"""
@@ -28,31 +27,31 @@ def message_to_output(self, message: dict) -> Tuple[str, str]:
single output found in the message in that order of priority:
image > text data > text print > error > nothing
"""
- message_type = 'None'
- if 'data' in message:
- if 'image/png' in message['data']:
- message_type = 'image'
+ message_type = "None"
+ if "data" in message:
+ if "image/png" in message["data"]:
+ message_type = "image"
# output an image (from plt.plot or plt.imshow)
- out = message['data']['image/png']
- elif 'text/html' in message['data']:
- message_type = 'text'
+ out = message["data"]["image/png"]
+ elif "text/html" in message["data"]:
+ message_type = "text"
# output some html text (like a pandas dataframe)
- out = message['data']['text/html']
+ out = message["data"]["text/html"]
else:
- message_type = 'text'
+ message_type = "text"
# output data as str (for example if code="a=10\na")
- out = message['data']['text/plain']
- elif 'name' in message and message['name'] == "stdout":
- message_type = 'text'
+ out = message["data"]["text/plain"]
+ elif "name" in message and message["name"] == "stdout":
+ message_type = "text"
# output a print (print("Hello World"))
- out = message['text']
- elif 'traceback' in message:
- message_type = 'error'
+ out = message["text"]
+ elif "traceback" in message:
+ message_type = "error"
# output an error
- out = '\n'.join(message['traceback'])
+ out = "\n".join(message["traceback"])
else:
- message_type = 'text'
- out = ''
+ message_type = "text"
+ out = ""
return out, message_type
def run_block(self, block, code: str):
@@ -65,15 +64,17 @@ def run_block(self, block, code: str):
code: String representing a piece of Python code to execute
"""
worker = Worker(self, code)
+ # Change color to running
+ block.run_color = 1
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)
+ block.scene().threadpool.start(worker)
def run_queue(self):
- """ Runs the next code in the queue """
+ """Runs the next code in the queue"""
self.busy = True
if self.execution_queue == []:
self.busy = False
@@ -114,8 +115,8 @@ def get_message(self) -> Tuple[str, bool]:
"""
done = False
try:
- message = self.client.get_iopub_msg(timeout=1000)['content']
- if 'execution_state' in message and message['execution_state'] == 'idle':
+ message = self.client.get_iopub_msg(timeout=1000)["content"]
+ if "execution_state" in message and message["execution_state"] == "idle":
done = True
except queue.Empty:
message = None
diff --git a/opencodeblocks/graphics/pyeditor/pyeditor.py b/opencodeblocks/graphics/pyeditor/pyeditor.py
index 7d34c28d..29e62629 100644
--- a/opencodeblocks/graphics/pyeditor/pyeditor.py
+++ b/opencodeblocks/graphics/pyeditor/pyeditor.py
@@ -4,7 +4,7 @@
""" 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,
@@ -19,10 +19,6 @@
from opencodeblocks.graphics.theme_manager import theme_manager
from opencodeblocks.blocks.block import OCBBlock
-from opencodeblocks.graphics.kernel import Kernel
-
-kernel = Kernel()
-threadpool = QThreadPool()
if TYPE_CHECKING:
from opencodeblocks.graphics.view import OCBView
@@ -44,8 +40,6 @@ def __init__(self, block: OCBBlock):
super().__init__(None)
self._mode = "NOOP"
self.block = block
- self.kernel = kernel
- self.threadpool = threadpool
self.history = EditorHistory(self)
self.pressingControl = False
diff --git a/opencodeblocks/graphics/theme.py b/opencodeblocks/graphics/theme.py
index 57171f3f..30182858 100644
--- a/opencodeblocks/graphics/theme.py
+++ b/opencodeblocks/graphics/theme.py
@@ -9,7 +9,7 @@
class Theme:
- """ Class holding the details of a specific theme"""
+ """Class holding the details of a specific theme"""
def __init__(self, name: str, json_str: str = "{}"):
"""
@@ -23,7 +23,7 @@ def __init__(self, name: str, json_str: str = "{}"):
"keyword_color": "#569CD6",
"classname_color": "#4EC9B0",
"literal_color": "#7FB347",
- "operator_color": "#D8D8D8"
+ "operator_color": "#D8D8D8",
}
for (property_name, property_value) in known_properties.items():
if property_name in json_obj:
@@ -33,7 +33,7 @@ def __init__(self, name: str, json_str: str = "{}"):
self.name = name
def apply_to_lexer(self, lexer: QsciLexerPython):
- """ Make the given lexer follow the theme """
+ """Make the given lexer follow the theme"""
lexer.setDefaultPaper(QColor("#1E1E1E"))
lexer.setDefaultColor(QColor("#D4D4D4"))
@@ -51,16 +51,10 @@ def apply_to_lexer(self, lexer: QsciLexerPython):
for string_type in string_types:
lexer.setColor(QColor(self.string_color), string_type)
- lexer.setColor(
- QColor(
- self.function_color),
- QsciLexerPython.FunctionMethodName)
+ lexer.setColor(QColor(self.function_color), QsciLexerPython.FunctionMethodName)
lexer.setColor(QColor(self.keyword_color), QsciLexerPython.Keyword)
lexer.setColor(QColor(self.classname_color), QsciLexerPython.ClassName)
lexer.setColor(QColor(self.literal_color), QsciLexerPython.Number)
lexer.setColor(QColor(self.operator_color), QsciLexerPython.Operator)
- lexer.setColor(
- QColor(
- self.comment_color),
- QsciLexerPython.CommentBlock)
+ lexer.setColor(QColor(self.comment_color), QsciLexerPython.CommentBlock)
lexer.setColor(QColor(self.comment_color), QsciLexerPython.Comment)
diff --git a/opencodeblocks/graphics/view.py b/opencodeblocks/graphics/view.py
index 30a927ac..152f192c 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.codeblock import OCBCodeBlock
EPS: float = 1e-10 # To check if blocks are of size 0
@@ -315,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/opencodeblocks/graphics/widget.py b/opencodeblocks/graphics/widget.py
index a3910319..d5025355 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/opencodeblocks/graphics/window.py b/opencodeblocks/graphics/window.py
index 3e2a6af4..e0017f7c 100644
--- a/opencodeblocks/graphics/window.py
+++ b/opencodeblocks/graphics/window.py
@@ -302,7 +302,8 @@ def createNewMdiChild(self, filename: str = None):
ocb_widget = OCBWidget()
if filename is not None:
ocb_widget.scene.load(filename)
- ocb_widget.savepath = filename
+ if filename.split(".")[-1] == "ipyg":
+ ocb_widget.savepath = filename
return self.mdiArea.addSubWindow(ocb_widget)
def onFileNew(self):
@@ -374,7 +375,6 @@ def oneFileSaveAsJupyter(self) -> bool:
)
if filename == "":
return False
- current_window.savepath = filename
current_window.saveAsJupyter()
self.statusbar.showMessage(
f"Successfully saved ipygraph as jupter notebook at {current_window.savepath}",
diff --git a/opencodeblocks/graphics/worker.py b/opencodeblocks/graphics/worker.py
index 774aa3c4..ea339952 100644
--- a/opencodeblocks/graphics/worker.py
+++ b/opencodeblocks/graphics/worker.py
@@ -44,8 +44,8 @@ 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()
- self.signals.finished_block.emit()
def run(self):
"""Execute the run_code method asynchronously."""
diff --git a/opencodeblocks/qss/__init__.py b/opencodeblocks/qss/__init__.py
index 16024f9c..208e3fc4 100644
--- a/opencodeblocks/qss/__init__.py
+++ b/opencodeblocks/qss/__init__.py
@@ -12,10 +12,10 @@
def loadStylesheets(filenames: List[str]):
- styles = ''
+ styles = ""
for filename in filenames:
file = QFile(filename)
file.open(QFile.ReadOnly | QFile.Text)
stylesheet = file.readAll()
- styles += "\n" + str(stylesheet, encoding='utf-8')
+ styles += "\n" + str(stylesheet, encoding="utf-8")
QApplication.instance().setStyleSheet(styles)
diff --git a/opencodeblocks/scene/clipboard.py b/opencodeblocks/scene/clipboard.py
index 3d7b0320..c7fd021c 100644
--- a/opencodeblocks/scene/clipboard.py
+++ b/opencodeblocks/scene/clipboard.py
@@ -45,15 +45,13 @@ def _serializeSelected(self, delete=False) -> OrderedDict:
selected_blocks, selected_edges = self.scene.sortedSelectedItems()
selected_sockets = {}
- for block in selected_blocks: # Gather selected sockets
+ # Gather selected sockets
+ for block in selected_blocks:
for socket in block.sockets_in + block.sockets_out:
selected_sockets[socket.id] = socket
- for (
- edge
- ) in (
- selected_edges
- ): # Filter edges that are not fully connected to selected sockets
+ # Filter edges that are not fully connected to selected sockets
+ for edge in selected_edges:
if (
edge.source_socket.id not in selected_sockets
or edge.destination_socket.id not in selected_sockets
diff --git a/opencodeblocks/scene/from_ipynb_conversion.py b/opencodeblocks/scene/from_ipynb_conversion.py
index ab9b41c7..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,16 +37,19 @@ 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
next_block_y_pos: float = 0
+ next_block_id = 0
for cell in data["cells"]:
if "cell_type" not in cell or cell["cell_type"] not in ["code", "markdown"]:
@@ -49,20 +58,32 @@ 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
block_data = {
- "id": len(blocks_data),
+ "id": next_block_id,
"block_type": BLOCK_TYPE_TO_NAME[block_type],
"width": block_width,
"height": block_height,
@@ -93,6 +114,7 @@ def get_blocks_data(data: OrderedDict) -> List[OrderedDict]:
next_block_y_pos += block_height + MARGIN_BETWEEN_BLOCKS_Y
blocks_data.append(block_data)
+ next_block_id += 1
adujst_markdown_blocks_width(blocks_data)
@@ -144,9 +166,13 @@ def get_edges_data(blocks_data: OrderedDict) -> OrderedDict:
]
edges_data: List[OrderedDict] = []
+ greatest_block_id: int = 0
+ if len(blocks_data) > 0:
+ greatest_block_id = blocks_data[-1]["id"]
+
for i in range(1, len(code_blocks)):
- socket_id_out = len(blocks_data) + 2 * i
- socket_id_in = len(blocks_data) + 2 * i + 1
+ socket_id_out = greatest_block_id + 2 * i + 2
+ socket_id_in = greatest_block_id + 2 * i + 1
code_blocks[i - 1]["sockets"].append(
get_output_socket_data(socket_id_out, code_blocks[i - 1]["width"])
)
diff --git a/opencodeblocks/scene/scene.py b/opencodeblocks/scene/scene.py
index d00d641c..60b86814 100644
--- a/opencodeblocks/scene/scene.py
+++ b/opencodeblocks/scene/scene.py
@@ -5,24 +5,23 @@
import math
import json
+from os import path
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
-from opencodeblocks import blocks
-
from opencodeblocks.core.serializable import Serializable
from opencodeblocks.blocks.block import OCBBlock
from opencodeblocks.graphics.edge import OCBEdge
from opencodeblocks.scene.clipboard import SceneClipboard
from opencodeblocks.scene.history import SceneHistory
+from opencodeblocks.graphics.kernel import Kernel
from opencodeblocks.scene.from_ipynb_conversion import ipynb_to_ipyg
from opencodeblocks.scene.to_ipynb_conversion import ipyg_to_ipynb
-
-import networkx as nx
+from opencodeblocks import blocks
class OCBScene(QGraphicsScene, Serializable):
@@ -59,6 +58,9 @@ def __init__(
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."""
@@ -176,8 +178,15 @@ 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__("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
+ """
+ Load the json data into an ordered dict
+
Args:
filepath: Path to the file to load.
"""
@@ -191,6 +200,7 @@ def clear(self):
return super().clear()
def serialize(self) -> OrderedDict:
+ """Serialize the scene into a dict."""
blocks = []
edges = []
for item in self.items():
@@ -208,17 +218,6 @@ def serialize(self) -> OrderedDict:
]
)
- def create_graph(self) -> nx.DiGraph:
- """Create a networkx graph from the scene."""
- edges = []
- for item in self.items():
- if isinstance(item, OCBEdge):
- edges.append(item)
- graph = nx.DiGraph()
- for edge in edges:
- graph.add_edge(edge.source_socket.block, edge.destination_socket.block)
- return graph
-
def create_block_from_file(self, filepath: str, x: float = 0, y: float = 0):
"""Create a new block from a .ocbb file"""
with open(filepath, "r", encoding="utf-8") as file:
@@ -258,8 +257,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"]:
diff --git a/tests/assets/complex.ipynb b/tests/assets/complex.ipynb
new file mode 100644
index 00000000..0fd86aa3
--- /dev/null
+++ b/tests/assets/complex.ipynb
@@ -0,0 +1,914 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "# Deep Neural Networks\n",
+ "\n",
+ "---\n",
+ "\n",
+ "\n",
+ "\n",
+ "\n",
+ "> Version: **1.0**\n",
+ "\n",
+ "\n",
+ "\n",
+ "\n",
+ "# 1 . Implementation of a Neural Network\n",
+ "\n",
+ "In this exercise you will learn how to implement from scratch a deep neural network.\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Set-up\n",
+ "\n",
+ "Firstly you will import all the packages used through the notebook. "
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 1,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "c:\\users\\efabr\\miniconda3\\lib\\site-packages\\numpy\\_distributor_init.py:32: UserWarning: loaded more than 1 DLL from .libs:\n",
+ "c:\\users\\efabr\\miniconda3\\lib\\site-packages\\numpy\\.libs\\libopenblas.QVLO2T66WEPI7JZ63PS3HMOHFEY472BC.gfortran-win_amd64.dll\n",
+ "c:\\users\\efabr\\miniconda3\\lib\\site-packages\\numpy\\.libs\\libopenblas.WCDJNK7YVMPZQ2ME2ZZHJJRJ3JIKNDB7.gfortran-win_amd64.dll\n",
+ " stacklevel=1)\n"
+ ]
+ }
+ ],
+ "source": [
+ "import numpy as np\n",
+ "import matplotlib.pyplot as plt\n",
+ "import h5py\n",
+ "\n",
+ "%matplotlib inline\n",
+ "\n",
+ "%load_ext autoreload\n",
+ "%autoreload 2\n",
+ "\n",
+ "np.random.seed(3)\n",
+ "\n",
+ "from utils import *"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Initialization\n",
+ "\n",
+ "Start by defining a function that allows to initialize the parameters of a deep neural network where the dimensions. The number of units in the different layers are passed as argument with `layer_dims`.\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 42,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "def initialization(layer_dims):\n",
+ "\n",
+ " np.random.seed(5)\n",
+ " parameters = {}\n",
+ " L = len(layer_dims) \n",
+ " \n",
+ " for l in range(1, L):\n",
+ " parameters['W' + str(l)] = np.random.randn(layer_dims[l],layer_dims[l-1])*0.01\n",
+ " parameters['b' + str(l)] = np.zeros((1,layer_dims[l]))\n",
+ " \n",
+ " return parameters"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 44,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "W1 = [[ 0.00441227 -0.0033087 0.02430771 -0.00252092 0.0010961 ]\n",
+ " [ 0.01582481 -0.00909232 -0.00591637 0.00187603 -0.0032987 ]\n",
+ " [-0.01192765 -0.00204877 -0.00358829 0.00603472 -0.01664789]\n",
+ " [-0.00700179 0.01151391 0.01857331 -0.0151118 0.00644848]]\n",
+ "b1 = [[0. 0. 0. 0.]]\n",
+ "W2 = [[-9.80607885e-03 -8.56853155e-03 -8.71879183e-03 -4.22507929e-03]\n",
+ " [ 9.96439827e-03 7.12421271e-03 5.91442432e-04 -3.63310878e-03]\n",
+ " [ 3.28884293e-05 -1.05930442e-03 7.93053319e-03 -6.31571630e-03]]\n",
+ "b2 = [[0. 0. 0.]]\n"
+ ]
+ }
+ ],
+ "source": [
+ "parameters = initialization([5,4,3])\n",
+ "print(\"W1 = \" + str(parameters[\"W1\"]))\n",
+ "print(\"b1 = \" + str(parameters[\"b1\"]))\n",
+ "print(\"W2 = \" + str(parameters[\"W2\"]))\n",
+ "print(\"b2 = \" + str(parameters[\"b2\"]))"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Forward propagation\n",
+ "\n",
+ "The forward propagation has been split in different steps. Firstly, the linear forward module computes the following equations:\n",
+ "\n",
+ "$$Z^{[l]} = W^{[l]}A^{[l-1]} +b^{[l]}\\tag{4}$$\n",
+ "\n",
+ "where $A^{[0]} = X$. \n",
+ "\n",
+ "Define a function to compute $Z^{[l]}$"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 45,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "def linear_forward(A, W, b):\n",
+ " Z = W@A + b\n",
+ " cache = (A, W, b)\n",
+ " \n",
+ " return Z, cache"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 46,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Z = [[-0.67356113 0.67062057]]\n"
+ ]
+ }
+ ],
+ "source": [
+ "A, W, b = linear_forward_test()\n",
+ "\n",
+ "Z, linear_cache = linear_forward(A, W, b)\n",
+ "print(\"Z = \" + str(Z))"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "**Expected output**:\n",
+ "\n",
+ "
\n",
+ " \n",
+ " \n",
+ " **Z** | \n",
+ " [[ -0.67356113 0.67062057]] | \n",
+ "
\n",
+ " \n",
+ "
"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Activation Functions\n",
+ "\n",
+ "In the first notebook you implemented the sigmoid function:\n",
+ "\n",
+ "- **Sigmoid**: $\\sigma(Z) = \\sigma(W A + b) = \\frac{1}{ 1 + e^{-(W A + b)}}$.\n",
+ "\n",
+ "In this notebook, you will need to implement the ReLU activation defined as:\n",
+ "\n",
+ "- **ReLU**: $A = RELU(Z) = max(0, Z)$. \n",
+ "\n",
+ "Complete the function below that computes the ReLU an activation fucntion."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 47,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "def relu(Z):\n",
+ "\n",
+ " A = np.maximum(0,Z)\n",
+ " cache = Z \n",
+ " \n",
+ " return A, cache"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "You have implemented a function that determines the linear foward step. You will now combine the output of this function with either a sigmoid() or a relu() activation function. "
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 48,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "def forward_one(A_prev, W, b, activation):\n",
+ " Z, linear_cache = linear_forward(A_prev, W, b)\n",
+ " if activation == 'relu' :\n",
+ " A, activation_cache = relu(Z)\n",
+ " \n",
+ " elif activation == 'sigmoid' :\n",
+ " A, activation_cache = sigmoid(Z)\n",
+ " \n",
+ " cache = (linear_cache, activation_cache)\n",
+ "\n",
+ " return A, cache"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 49,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "With sigmoid: A = [[0.96313579 0.22542973]]\n",
+ "With ReLU: A = [[3.26295337 0. ]]\n"
+ ]
+ }
+ ],
+ "source": [
+ "A_prev, W, b = forward_one_test()\n",
+ "\n",
+ "A, linear_activation_cache = forward_one(A_prev, W, b, activation = \"sigmoid\")\n",
+ "print(\"With sigmoid: A = \" + str(A))\n",
+ "\n",
+ "A, linear_activation_cache = forward_one(A_prev, W, b, activation = \"relu\")\n",
+ "print(\"With ReLU: A = \" + str(A))"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Forward propagation model\n",
+ "\n",
+ "The structure you will implement in this exercise consists on $L-1$ layers using a ReLU activation function and a last layer using a sigmoid.\n",
+ "Implement the forward propagation of the above model."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 50,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "def forward_all(X, parameters):\n",
+ "\n",
+ " caches = []\n",
+ " A = X\n",
+ " L = len(parameters) // 2 \n",
+ " \n",
+ " for l in range(1, L):\n",
+ " A_prev = A \n",
+ " # Implement L-1 layers of RELU and for each layer add \"cache\" to the \"caches\" list.\n",
+ " A, cache = forward_one(A,parameters[\"W\"+ str(l)],parameters[\"b\"+ str(l)],\"relu\")\n",
+ " caches.append(cache)\n",
+ " AL, cache = forward_one(A,parameters[\"W\"+ str(L)],parameters[\"b\"+ str(L)],\"sigmoid\")\n",
+ " caches.append(cache)\n",
+ " \n",
+ " \n",
+ " return AL, caches"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 51,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "AL = [[0.03921668 0.70498921 0.19734387 0.04728177]]\n",
+ "Length of caches list = 3\n"
+ ]
+ }
+ ],
+ "source": [
+ "X, parameters = forward_all_test()\n",
+ "AL, caches = forward_all(X, parameters)\n",
+ "print(\"AL = \" + str(AL))\n",
+ "print(\"Length of caches list = \" + str(len(caches)))"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Cost function\n",
+ "\n",
+ "You will now compute the cross-entropy cost $J$, for all the training set using the following formula: $$-\\frac{1}{m} \\sum\\limits_{i = 1}^{m} (y^{(i)}\\log\\left(a^{[L] (i)}\\right) + (1-y^{(i)})\\log\\left(1- a^{[L](i)}\\right)) \\tag{7}$$\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 52,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "def cost_function(AL, Y):\n",
+ " \n",
+ " m = Y.shape[1]\n",
+ "\n",
+ " cost = (-1/m)*np.sum((np.dot(Y,np.log(AL.T))+np.dot((1-Y),np.log(1-AL).T))) \n",
+ " cost = np.squeeze(cost) # Eliminates useless dimensionality for the variable cost.\n",
+ " \n",
+ " return cost"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 53,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "cost = 0.2797765635793422\n"
+ ]
+ }
+ ],
+ "source": [
+ "Y, AL = compute_cost()\n",
+ "print(\"cost = \" + str(cost_function(AL, Y)))"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "\n",
+ "\n",
+ " \n",
+ " **cost** | \n",
+ " 0.2797765635793422 | \n",
+ "
\n",
+ "
"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Backpropagation \n",
+ "\n",
+ "You will now implement the functions that will help you compute the gradient of the loss function with respect to the different parameters.\n",
+ "\n",
+ "To move backward in the computational graph you need to apply the chain rule."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Linear backward\n",
+ "\n",
+ "For each layer $l$, the linear part is: $Z^{[l]} = W^{[l]} A^{[l-1]} + b^{[l]}$ (followed by an activation).\n",
+ "\n",
+ "Suppose you have already calculated the derivative $dZ^{[l]} = \\frac{\\partial \\mathcal{L} }{\\partial Z^{[l]}}$. You want to get $(dW^{[l]}, db^{[l]}, dA^{[l-1]})$.\n",
+ "\n",
+ "\n",
+ "The three outputs $(dW^{[l]}, db^{[l]}, dA^{[l-1]})$ are computed using the input $dZ^{[l]}$. The formulas you saw in class are:\n",
+ "$$ dW^{[l]} = \\frac{\\partial \\mathcal{J} }{\\partial W^{[l]}} = \\frac{1}{m} dZ^{[l]} A^{[l-1] T} \\tag{8}$$\n",
+ "$$ db^{[l]} = \\frac{\\partial \\mathcal{J} }{\\partial b^{[l]}} = \\frac{1}{m} \\sum_{i = 1}^{m} dZ^{[l](i)}\\tag{9}$$\n",
+ "$$ dA^{[l-1]} = \\frac{\\partial \\mathcal{L} }{\\partial A^{[l-1]}} = W^{[l] T} dZ^{[l]} \\tag{10}$$\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 54,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "def linear_backward(dZ, cache):\n",
+ " A_prev, W, b = cache\n",
+ " m = A_prev.shape[1]\n",
+ " \n",
+ " dW = 1/m * dZ@A_prev.T\n",
+ " db = 1/m * np.sum(dZ,1, keepdims = True)\n",
+ " dA_prev = W.T@dZ\n",
+ " \n",
+ " return dA_prev, dW, db\n",
+ " "
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 55,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "dA_prev = [[ 1.62477986e-01 2.08119187e+00 -1.34890293e+00 -8.08822550e-01]\n",
+ " [ 1.25651742e-02 -2.21287224e-01 -5.90636554e-01 4.05614891e-03]\n",
+ " [ 1.98659671e-01 2.39946554e+00 -1.86852905e+00 -9.65910523e-01]\n",
+ " [ 3.18813678e-01 -9.92645222e-01 -6.57125623e-01 -1.46564901e-01]\n",
+ " [ 2.48593418e-01 -1.19723579e+00 -4.44132647e-01 -6.09748046e-04]]\n",
+ "dW = [[-1.05705158 -0.98560069 -0.54049797 0.10982291 0.53086144]\n",
+ " [ 0.71089562 1.01447326 -0.10518156 0.34944625 -0.12867032]\n",
+ " [ 0.46569162 0.31842359 0.30629837 -0.01104559 -0.19524287]]\n",
+ "db = [[ 0.5722591 ]\n",
+ " [ 0.04780547]\n",
+ " [-0.38497696]]\n"
+ ]
+ }
+ ],
+ "source": [
+ "# Set up some test inputs\n",
+ "dZ, linear_cache = linear_backward_test()\n",
+ "\n",
+ "dA_prev, dW, db = linear_backward(dZ, linear_cache)\n",
+ "\n",
+ "print (\"dA_prev = \"+ str(dA_prev))\n",
+ "print (\"dW = \" + str(dW))\n",
+ "print (\"db = \" + str(db))\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "** Expected Output**:\n",
+ " \n",
+ "```\n",
+ "dA_prev = \n",
+ "[[ 1.62477986e-01 2.08119187e+00 -1.34890293e+00 -8.08822550e-01]\n",
+ " [ 1.25651742e-02 -2.21287224e-01 -5.90636554e-01 4.05614891e-03]\n",
+ " [ 1.98659671e-01 2.39946554e+00 -1.86852905e+00 -9.65910523e-01]\n",
+ " [ 3.18813678e-01 -9.92645222e-01 -6.57125623e-01 -1.46564901e-01]\n",
+ " [ 2.48593418e-01 -1.19723579e+00 -4.44132647e-01 -6.09748046e-04]]\n",
+ "dW = \n",
+ "[[-1.05705158 -0.98560069 -0.54049797 0.10982291 0.53086144]\n",
+ " [ 0.71089562 1.01447326 -0.10518156 0.34944625 -0.12867032]\n",
+ " [ 0.46569162 0.31842359 0.30629837 -0.01104559 -0.19524287]]\n",
+ "db = \n",
+ "[[ 0.5722591 ]\n",
+ " [ 0.04780547]\n",
+ " [-0.38497696]]\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Activation Functions\n",
+ "\n",
+ "Now you need to write the code that computes the derivatives for the activation functions. You have learned the derivatives for the sigmoid and the ReLU during theory class.\n",
+ "Complete the two function below."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 56,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "def sigmoid_backward(dA, cache): \n",
+ " Z = cache\n",
+ " \n",
+ " s = Z*(1-Z)\n",
+ " dZ = dA*s\n",
+ " \n",
+ " return dZ"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 57,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "def relu_backward(dA, cache):\n",
+ " \n",
+ " Z = cache \n",
+ " dZ = np.array(dA, copy=True) # convert dz to an array.\n",
+ " dZ = dZ*np.where(Z>0,1,0)\n",
+ " return dZ"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### One backpropagation step\n",
+ "\n",
+ "Next, you will create a function that implements one step of backpropagation,"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 58,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "def backward_one(dA, cache, activation):\n",
+ " linear_cache, activation_cache = cache \n",
+ " if activation == \"relu\":\n",
+ " dZ = relu_backward(dA,activation_cache)\n",
+ " dA_prev, dW, db = linear_backward(dZ, linear_cache)\n",
+ " elif activation == \"sigmoid\":\n",
+ " dZ = sigmoid_backward(dA,activation_cache)\n",
+ " dA_prev, dW, db = linear_backward(dZ, linear_cache)\n",
+ " \n",
+ " return dA_prev, dW, db"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 59,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "sigmoid:\n",
+ "dA_prev = [[ 0.00410547 0.03685307]\n",
+ " [-0.01417887 -0.12727776]\n",
+ " [ 0.00764463 0.06862266]]\n",
+ "dW = [[ 0.03231386 -0.0904648 0.02919517]]\n",
+ "db = [[0.06163813]]\n",
+ "\n",
+ "relu:\n",
+ "dA_prev = [[ 0.01679913 0.16610885]\n",
+ " [-0.05801838 -0.57368247]\n",
+ " [ 0.031281 0.30930474]]\n",
+ "dW = [[ 0.14820532 -0.40668077 0.13325465]]\n",
+ "db = [[0.27525652]]\n"
+ ]
+ }
+ ],
+ "source": [
+ "dAL, linear_activation_cache = linear_activation_backward_test()\n",
+ "\n",
+ "dA_prev, dW, db = backward_one(dAL, linear_activation_cache, \"sigmoid\")\n",
+ "print (\"sigmoid:\")\n",
+ "print (\"dA_prev = \"+ str(dA_prev))\n",
+ "print (\"dW = \" + str(dW))\n",
+ "print (\"db = \" + str(db) + \"\\n\")\n",
+ "\n",
+ "dA_prev, dW, db = backward_one(dAL, linear_activation_cache, activation = \"relu\")\n",
+ "print (\"relu:\")\n",
+ "print (\"dA_prev = \"+ str(dA_prev))\n",
+ "print (\"dW = \" + str(dW))\n",
+ "print (\"db = \" + str(db))"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Backpropagation model\n",
+ "\n",
+ "Now you will put all together to compute the backward function for the whole network. \n",
+ "In the backpropagation step, you will use the variables you stored in cache in the `forward_all` function to compute the gradients. You will iterate from the last layer backwards to layer $1$.\n",
+ "\n",
+ "You need to start by computing the derivative of the loss function with respect to $A^{[L]}$. And propagate this gradient backward thourgh all the layers in the network.\n",
+ "\n",
+ "You need to save each dA, dW and db in the grads dictionary. "
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 60,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "def backward_all(AL, Y, caches):\n",
+ " grads = {}\n",
+ " L = len(caches) \n",
+ " m = AL.shape[1]\n",
+ " Y = Y.reshape(AL.shape) \n",
+ "\n",
+ " dZ = AL-Y\n",
+ " current_cache = caches[L-1]\n",
+ " grads[\"dA\" + str(L-1)], grads[\"dW\" + str(L)], grads[\"db\" + str(L)] = linear_backward(dZ, current_cache[0])\n",
+ " dAL = grads[\"dA\" + str(L-1)]\n",
+ " for l in reversed(range(L-1)):\n",
+ " current_cache = caches[l]\n",
+ " dA_prev_temp, dW_temp, db_temp = backward_one(dAL, current_cache, \"relu\")\n",
+ " grads[\"dA\" + str(l)] = dA_prev_temp\n",
+ " grads[\"dW\" + str(l + 1)] = dW_temp\n",
+ " grads[\"db\" + str(l + 1)] = db_temp\n",
+ " return grads"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 61,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "dW1 = [[0.41642713 0.07927654 0.14011329 0.10664197]\n",
+ " [0. 0. 0. 0. ]\n",
+ " [0.05365169 0.01021384 0.01805193 0.01373955]]\n",
+ "db1 = [[-0.22346593]\n",
+ " [ 0. ]\n",
+ " [-0.02879093]]\n",
+ "dA1 = [[-0.80745758 -0.44693186]\n",
+ " [ 0.88640102 0.49062745]\n",
+ " [-0.10403132 -0.05758186]]\n"
+ ]
+ }
+ ],
+ "source": [
+ "AL, Y_assess, caches = backward_all_test()\n",
+ "grads = backward_all(AL, Y_assess, caches)\n",
+ "print_grads(grads)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "**Expected Output**\n",
+ "\n",
+ "\n",
+ " \n",
+ " \n",
+ " dW1 | \n",
+ " [[ 0.41010002 0.07807203 0.13798444 0.10502167]\n",
+ " [ 0. 0. 0. 0. ]\n",
+ " [ 0.05283652 0.01005865 0.01777766 0.0135308 ]] | \n",
+ "
\n",
+ " \n",
+ " \n",
+ " db1 | \n",
+ " [[-0.22007063]\n",
+ " [ 0. ]\n",
+ " [-0.02835349]] | \n",
+ "
\n",
+ " \n",
+ " \n",
+ " dA1 | \n",
+ " [[ 0.12913162 -0.44014127]\n",
+ " [-0.14175655 0.48317296]\n",
+ " [ 0.01663708 -0.05670698]] | \n",
+ "\n",
+ "
\n",
+ "
\n",
+ "\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Gradient Descent\n",
+ "\n",
+ "Finally you can update the parameters of the model according: \n",
+ "\n",
+ "$$ W^{[l]} = W^{[l]} - \\alpha \\text{ } dW^{[l]} $$\n",
+ "$$ b^{[l]} = b^{[l]} - \\alpha \\text{ } db^{[l]} $$\n",
+ "\n",
+ "where $\\alpha$ is the learning rate. After computing the updated parameters, store them in the parameters dictionary. "
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 62,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "def gradient_descent(parameters, grads, learning_rate):\n",
+ " L = len(parameters) // 2 \n",
+ "\n",
+ " for l in range(L):\n",
+ " parameters[\"W\" + str(l+1)] = parameters[\"W\"+ str(l+1)] - learning_rate * grads[\"dW\"+ str(l+1)]\n",
+ " parameters[\"b\" + str(l+1)] = parameters[\"b\"+ str(l+1)] - learning_rate * grads[\"db\"+ str(l+1)]\n",
+ " return parameters"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 63,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "W1 = [[-0.59562069 -0.09991781 -2.14584584 1.82662008]\n",
+ " [-1.76569676 -0.80627147 0.51115557 -1.18258802]\n",
+ " [-1.0535704 -0.86128581 0.68284052 2.20374577]]\n",
+ "b1 = [[-0.04659241]\n",
+ " [-1.28888275]\n",
+ " [ 0.53405496]]\n",
+ "W2 = [[-0.55569196 0.0354055 1.32964895]]\n",
+ "b2 = [[-0.84610769]]\n"
+ ]
+ }
+ ],
+ "source": [
+ "parameters, grads = gradient_descent_test_case()\n",
+ "parameters = gradient_descent(parameters, grads, 0.1)\n",
+ "\n",
+ "print (\"W1 = \"+ str(parameters[\"W1\"]))\n",
+ "print (\"b1 = \"+ str(parameters[\"b1\"]))\n",
+ "print (\"W2 = \"+ str(parameters[\"W2\"]))\n",
+ "print (\"b2 = \"+ str(parameters[\"b2\"]))"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "**Expected Output**:\n",
+ "\n",
+ " \n",
+ " \n",
+ " W1 | \n",
+ " [[-0.59562069 -0.09991781 -2.14584584 1.82662008]\n",
+ " [-1.76569676 -0.80627147 0.51115557 -1.18258802]\n",
+ " [-1.0535704 -0.86128581 0.68284052 2.20374577]] | \n",
+ "
\n",
+ " \n",
+ " \n",
+ " b1 | \n",
+ " [[-0.04659241]\n",
+ " [-1.28888275]\n",
+ " [ 0.53405496]] | \n",
+ "
\n",
+ " \n",
+ " W2 | \n",
+ " [[-0.55569196 0.0354055 1.32964895]] | \n",
+ "
\n",
+ " \n",
+ " \n",
+ " b2 | \n",
+ " [[-0.84610769]] | \n",
+ "
\n",
+ "
\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "You can now create a deep neural network combining all the functions defined above."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 64,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "def dnn(X, Y, layers_dims, learning_rate = 0.009, num_iterations = 100, print_cost=True):#lr was 0.009\n",
+ " costs = [] \n",
+ " \n",
+ " parameters = initialization(layers_dims)\n",
+ " for i in range(0, num_iterations):\n",
+ " AL, caches = forward_all(X, parameters)\n",
+ " cost = cost_function(AL, Y)\n",
+ " costs.append(cost)\n",
+ " if print_cost:\n",
+ " print(cost)\n",
+ " grads = backward_all(AL, Y, caches)\n",
+ " parameters = gradient_descent(parameters, grads, learning_rate)\n",
+ " \n",
+ " \n",
+ " return parameters, costs"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "# 2 . Deep Neural Networks for Classification\n",
+ "\n",
+ "Consider now the dataset you used in the previous exercise. You solved the classification problem using Logistic Regression. Propose a Deep Neural Network architecture using the code you developed in the first part of this exercise that improves on the classification results of Logistic Regression."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 65,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "import numpy as np\n",
+ "import matplotlib.pyplot as plt\n",
+ "import h5py\n",
+ "import scipy\n",
+ "from PIL import Image\n",
+ "from scipy import ndimage\n",
+ "from lr_utils import load_dataset\n",
+ "\n",
+ "%matplotlib inline"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 66,
+ "metadata": {
+ "scrolled": true
+ },
+ "outputs": [],
+ "source": [
+ "train_set_x_orig, train_set_y, test_set_x_orig, test_set_y, classes = load_dataset()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 67,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "m_train = train_set_x_orig.shape[0]\n",
+ "m_test = test_set_x_orig.shape[0]\n",
+ "num_px = train_set_x_orig.shape[1] \n",
+ "train_set_x_flatten = train_set_x_orig.reshape(m_train,num_px * num_px * 3).T\n",
+ "test_set_x_flatten = test_set_x_orig.reshape(m_test,num_px * num_px * 3).T\n",
+ "train_set_x = train_set_x_flatten/255.\n",
+ "test_set_x = test_set_x_flatten/255."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 69,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "(12288, 209) 64\n"
+ ]
+ },
+ {
+ "ename": "TypeError",
+ "evalue": "_swapaxes_dispatcher() missing 2 required positional arguments: 'axis1' and 'axis2'",
+ "output_type": "error",
+ "traceback": [
+ "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m",
+ "\u001b[1;31mTypeError\u001b[0m Traceback (most recent call last)",
+ "\u001b[1;32m\u001b[0m in \u001b[0;36m\u001b[1;34m()\u001b[0m\n\u001b[0;32m 1\u001b[0m \u001b[0mprint\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mtrain_set_x_flatten\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mshape\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mnum_px\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m----> 2\u001b[1;33m \u001b[0mnp\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mswapaxes\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mtrain_set_x_flatten\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m 3\u001b[0m \u001b[0mdnn\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mtrain_set_x_flatten\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mtrain_set_y\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;33m[\u001b[0m\u001b[0mtrain_set_x_flatten\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mshape\u001b[0m\u001b[1;33m[\u001b[0m\u001b[1;36m0\u001b[0m\u001b[1;33m]\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;36m4\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;36m1\u001b[0m\u001b[1;33m]\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n",
+ "\u001b[1;32m<__array_function__ internals>\u001b[0m in \u001b[0;36mswapaxes\u001b[1;34m(*args, **kwargs)\u001b[0m\n",
+ "\u001b[1;31mTypeError\u001b[0m: _swapaxes_dispatcher() missing 2 required positional arguments: 'axis1' and 'axis2'"
+ ]
+ }
+ ],
+ "source": [
+ "print(train_set_x_flatten.shape, num_px)\n",
+ "np.swapaxes(train_set_x_flatten)\n",
+ "dnn(train_set_x_flatten, train_set_y, [train_set_x_flatten.shape[0], 4, 1])"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": []
+ }
+ ],
+ "metadata": {
+ "anaconda-cloud": {},
+ "coursera": {
+ "course_slug": "neural-networks-deep-learning",
+ "graded_item_id": "c4HO0",
+ "launcher_item_id": "lSYZM"
+ },
+ "kernelspec": {
+ "display_name": "Python 3",
+ "language": "python",
+ "name": "python3"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 2
+}
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/empty.ipynb b/tests/assets/empty.ipynb
new file mode 100644
index 00000000..c6f02f6f
--- /dev/null
+++ b/tests/assets/empty.ipynb
@@ -0,0 +1,32 @@
+{
+ "cells": [
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": []
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.6.4"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 2
+}
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/assets/flow_test.ipyg b/tests/assets/flow_test.ipyg
index 414cfd1c..0a20333b 100644
--- a/tests/assets/flow_test.ipyg
+++ b/tests/assets/flow_test.ipyg
@@ -749,7 +749,7 @@
}
},
"sockets": [],
- "text": "**Test flow tests the general behavior of flow execution:**\r\n\r\n\tExecuting 6 left should output 6\r\n\r\n\tExecuting 6 right should output 21 in block 8\r\n"
+ "text": "**Test flow tests the general behavior of flow execution:**\r\n\r\n\tExecuting 6 left should output 4\r\n\r\n\tExecuting 6 right should output 21 in block 8\r\n"
}
],
"edges": [
diff --git a/tests/assets/usual.ipynb b/tests/assets/usual.ipynb
new file mode 100644
index 00000000..8f60756f
--- /dev/null
+++ b/tests/assets/usual.ipynb
@@ -0,0 +1,194 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "# A report"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 1,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "c:\\users\\efabr\\miniconda3\\lib\\site-packages\\numpy\\_distributor_init.py:32: UserWarning: loaded more than 1 DLL from .libs:\n",
+ "c:\\users\\efabr\\miniconda3\\lib\\site-packages\\numpy\\.libs\\libopenblas.QVLO2T66WEPI7JZ63PS3HMOHFEY472BC.gfortran-win_amd64.dll\n",
+ "c:\\users\\efabr\\miniconda3\\lib\\site-packages\\numpy\\.libs\\libopenblas.WCDJNK7YVMPZQ2ME2ZZHJJRJ3JIKNDB7.gfortran-win_amd64.dll\n",
+ " stacklevel=1)\n"
+ ]
+ }
+ ],
+ "source": [
+ "# Imports\n",
+ "import numpy as np\n",
+ "import anotherlib as al"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "First we load some data"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 5,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "def loading_data(filename):\n",
+ " return np.random.random((20,20))"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 6,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "data = loading_data(\"hello.csv\")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Then do something with it"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 7,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "array([[3.63948558, 3.27076204, 3.48612965, 3.04451501, 3.36451054,\n",
+ " 3.49666763, 3.45160491, 3.41537927, 3.07652413, 3.66197315,\n",
+ " 3.18283939, 3.24002107, 3.55144998, 3.29783081, 3.57207105,\n",
+ " 3.52894424, 3.55476906, 3.8049996 , 3.97005352, 3.01381803],\n",
+ " [3.36975411, 3.79889692, 3.98689836, 3.19976399, 3.07865854,\n",
+ " 3.0357039 , 3.36727864, 3.44675425, 3.1485551 , 3.32505796,\n",
+ " 3.94280723, 3.93294761, 3.69969612, 3.39441561, 3.52488239,\n",
+ " 3.77017711, 3.31080687, 3.13851642, 3.8914675 , 3.22217344],\n",
+ " [3.68869882, 3.49143552, 3.94120228, 3.29677181, 3.49179313,\n",
+ " 3.13340065, 3.19535721, 3.19036895, 3.51647584, 3.56500995,\n",
+ " 3.71223623, 3.20364635, 3.74915529, 3.60763252, 3.33939492,\n",
+ " 3.32512116, 3.77610616, 3.4022048 , 3.58662752, 3.89863465],\n",
+ " [3.33233293, 3.67890573, 3.34158316, 3.20828747, 3.50817954,\n",
+ " 3.5434048 , 3.40567346, 3.34097188, 3.33359796, 3.54765666,\n",
+ " 3.78149215, 3.82061228, 3.58202782, 3.21714103, 3.78187063,\n",
+ " 3.14331254, 3.21238203, 3.05876572, 3.23641512, 3.07314562],\n",
+ " [3.96469085, 3.34664564, 3.80053502, 3.35413646, 3.2456791 ,\n",
+ " 3.9323963 , 3.14123278, 3.08543803, 3.32433011, 3.53758282,\n",
+ " 3.56606284, 3.283641 , 3.35755769, 3.57456616, 3.47084384,\n",
+ " 3.59188003, 3.7366276 , 3.1607076 , 3.70733558, 3.89854312],\n",
+ " [3.37855368, 3.37342495, 3.74749753, 3.32611455, 3.04470219,\n",
+ " 3.26913714, 3.55734092, 3.15093813, 3.06262451, 3.33520461,\n",
+ " 3.53713674, 3.73679289, 3.00179027, 3.67067575, 3.4524812 ,\n",
+ " 3.13490282, 3.17967332, 3.27475765, 3.63188686, 3.73635671],\n",
+ " [3.79496576, 3.13701034, 3.9137442 , 3.81506409, 3.45731737,\n",
+ " 3.71956806, 3.33353583, 3.22600394, 3.320342 , 3.50387212,\n",
+ " 3.52740845, 3.1051689 , 3.51774963, 3.39219758, 3.76537776,\n",
+ " 3.13219734, 3.2705521 , 3.51957676, 3.74631399, 3.39932089],\n",
+ " [3.55779719, 3.41516178, 3.07029595, 3.22289207, 3.28158917,\n",
+ " 3.79661708, 3.71722156, 3.36995609, 3.33689999, 3.59126513,\n",
+ " 3.7079066 , 3.49525298, 3.56939265, 3.96935088, 3.12164002,\n",
+ " 3.676332 , 3.97468624, 3.51783025, 3.21887996, 3.30523752],\n",
+ " [3.84033676, 3.32525466, 3.71113927, 3.9279179 , 3.002697 ,\n",
+ " 3.15725527, 3.03005171, 3.47318527, 3.64722962, 3.28827003,\n",
+ " 3.48847088, 3.19699471, 3.80627061, 3.18231442, 3.54125576,\n",
+ " 3.34299146, 3.73841012, 3.67166747, 3.9092137 , 3.0335553 ],\n",
+ " [3.7058796 , 3.57808271, 3.39623753, 3.92757397, 3.1351561 ,\n",
+ " 3.48092558, 3.33881418, 3.07534734, 3.4116249 , 3.83838506,\n",
+ " 3.48396284, 3.89211238, 3.09995526, 3.57699336, 3.04028569,\n",
+ " 3.70796281, 3.82625111, 3.00335284, 3.82351291, 3.45597272],\n",
+ " [3.97176208, 3.2377911 , 3.35122552, 3.008061 , 3.42012245,\n",
+ " 3.80543675, 3.05006123, 3.82998196, 3.21035844, 3.9324146 ,\n",
+ " 3.05317098, 3.61833738, 3.63764824, 3.52124233, 3.87902481,\n",
+ " 3.50641822, 3.3221031 , 3.7521446 , 3.71614348, 3.18424037],\n",
+ " [3.85059993, 3.70474109, 3.67106526, 3.46888781, 3.51912545,\n",
+ " 3.37423032, 3.03162456, 3.46525341, 3.16811541, 3.08918478,\n",
+ " 3.21506861, 3.49130976, 3.20194597, 3.70852144, 3.83703797,\n",
+ " 3.46643191, 3.56993413, 3.01070241, 3.0345923 , 3.38354211],\n",
+ " [3.1067765 , 3.17986932, 3.39974581, 3.81503415, 3.40999637,\n",
+ " 3.40962731, 3.89842993, 3.68748917, 3.46850976, 3.72833772,\n",
+ " 3.84667009, 3.51419885, 3.34223842, 3.75641038, 3.09752562,\n",
+ " 3.10430882, 3.17031172, 3.68318615, 3.26852823, 3.55773047],\n",
+ " [3.84328038, 3.12204164, 3.04845877, 3.06138172, 3.72354385,\n",
+ " 3.55180905, 3.52664241, 3.51290538, 3.41487721, 3.5808519 ,\n",
+ " 3.52471368, 3.1293383 , 3.97481537, 3.53333148, 3.17242121,\n",
+ " 3.33274109, 3.66331831, 3.48478873, 3.86629266, 3.39943898],\n",
+ " [3.01380158, 3.91737603, 3.92958052, 3.04459408, 3.33989635,\n",
+ " 3.46058188, 3.26540603, 3.86486319, 3.0671772 , 3.55414925,\n",
+ " 3.29465078, 3.73982667, 3.64077128, 3.79522347, 3.64958283,\n",
+ " 3.62710174, 3.27515289, 3.46606658, 3.65970842, 3.56858124],\n",
+ " [3.81765364, 3.18478292, 3.80875158, 3.08533944, 3.96073804,\n",
+ " 3.88650821, 3.98854391, 3.75026235, 3.99059743, 3.20337304,\n",
+ " 3.29633007, 3.8081186 , 3.38162993, 3.94841158, 3.14155517,\n",
+ " 3.63415005, 3.09000952, 3.87785134, 3.4880709 , 3.57519841],\n",
+ " [3.24256969, 3.50708353, 3.41433647, 3.28962266, 3.83899191,\n",
+ " 3.39102446, 3.6750535 , 3.65617164, 3.5215255 , 3.40090808,\n",
+ " 3.39341075, 3.62686904, 3.78749222, 3.68898565, 3.6632341 ,\n",
+ " 3.6559378 , 3.65910078, 3.47150939, 3.42101486, 3.91651094],\n",
+ " [3.94385503, 3.99691801, 3.93431865, 3.62193945, 3.47153953,\n",
+ " 3.75111669, 3.00165189, 3.66504947, 3.7384211 , 3.20962032,\n",
+ " 3.10356305, 3.25014366, 3.22037248, 3.36181067, 3.30191277,\n",
+ " 3.88477453, 3.12995043, 3.13019189, 3.31214712, 3.13234449],\n",
+ " [3.21909987, 3.22103605, 3.01240075, 3.27869683, 3.3607669 ,\n",
+ " 3.73113868, 3.30960944, 3.50728795, 3.89795141, 3.62701628,\n",
+ " 3.61512764, 3.62268613, 3.72038233, 3.4902457 , 3.8297139 ,\n",
+ " 3.55794527, 3.43277611, 3.8092867 , 3.28863261, 3.62435088],\n",
+ " [3.05355563, 3.74357485, 3.12245218, 3.88984309, 3.7065529 ,\n",
+ " 3.83742772, 3.49424284, 3.44208031, 3.10441662, 3.26142749,\n",
+ " 3.41649061, 3.57395604, 3.60538383, 3.19872492, 3.2726298 ,\n",
+ " 3.47467517, 3.72478131, 3.8025068 , 3.54180911, 3.11842077]])"
+ ]
+ },
+ "execution_count": 7,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "data = data + 3\n",
+ "data"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": []
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.6.4"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 2
+}
diff --git a/tests/integration/blocks/test_block.py b/tests/integration/blocks/test_block.py
index 02e6253e..93d42bce 100644
--- a/tests/integration/blocks/test_block.py
+++ b/tests/integration/blocks/test_block.py
@@ -11,21 +11,16 @@
from PyQt5.QtCore import QPointF
-from opencodeblocks.blocks.codeblock import OCBBlock
-from opencodeblocks.graphics.window import OCBWindow
-from opencodeblocks.graphics.widget import OCBWidget
+from opencodeblocks.blocks.block import OCBBlock
-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):
@@ -78,3 +73,6 @@ def testing_drag(msgQueue: CheckingQueue):
msgQueue.stop()
apply_function_inapp(self.window, testing_drag)
+
+ def test_finish(self):
+ self.window.close()
diff --git a/tests/integration/blocks/test_codeblock.py b/tests/integration/blocks/test_codeblock.py
index 7ab6b67b..3c304a0a 100644
--- a/tests/integration/blocks/test_codeblock.py
+++ b/tests/integration/blocks/test_codeblock.py
@@ -5,35 +5,30 @@
Integration tests for the OCBCodeBlocks.
"""
+import time
+import os
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:
-
@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()
+ """Setup reused variables."""
+ start_app(self)
- def test_run_python(self, qtbot: QtBot):
- """ run source code when run button is pressed. """
+ def test_run_python(self):
+ """run source code when run button is pressed."""
# Add a block with the source to the window
EXPRESSION = "3 + 5 * 2"
- SOURCE_TEST = f'''print({EXPRESSION})'''
+ SOURCE_TEST = f"""print({EXPRESSION})"""
expected_result = str(3 + 5 * 2)
test_block = OCBCodeBlock(title="CodeBlock test", source=SOURCE_TEST)
@@ -56,13 +51,54 @@ 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((test_block.transmitting_duration / 1000) + 0.2)
+ while test_block.run_color != 0:
+ time.sleep(0.1)
- # 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()
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
+ 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.run_color != 0:
+ time.sleep(0.1) # wait for the execution to finish.
+
+ time.sleep(0.1)
+
+ file_content = open(asset_path).read()
+
+ 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()
diff --git a/tests/integration/blocks/test_flow.py b/tests/integration/blocks/test_flow.py
index ab820dde..4bd3747e 100644
--- a/tests/integration/blocks/test_flow.py
+++ b/tests/integration/blocks/test_flow.py
@@ -9,85 +9,74 @@
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 TestExecutionFlow:
- """Execution flow"""
-
+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")
self.titles = [
"Test flow 5",
"Test flow 4",
- "Test flow 8",
"Test no connection 1",
+ "Test input only 2",
+ "Test output only 1",
]
- self.blocks_to_run = [None] * len(self.titles)
+ 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_flow_left(self):
- """run block and previous blocks when pressing left run."""
+ 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_run(msgQueue: CheckingQueue):
+ def testing_no_duplicates(msgQueue: CheckingQueue):
- block_to_run: OCBCodeBlock = self.blocks_to_run[
- self.titles.index("Test flow 5")
- ]
- block_to_not_run: OCBCodeBlock = self.blocks_to_run[
- self.titles.index("Test flow 4")
- ]
+ block_to_run: OCBCodeBlock = self.blocks_to_run[0]
def run_block():
- block_to_run.run_left()
+ block_to_run.run_right()
- # Run the execution in a separate thread
- # to give time for the outputs to show before checking them
msgQueue.run_lambda(run_block)
- time.sleep(0.5)
+ time.sleep((block_to_run.transmitting_duration / 1000) + 0.2)
+ while block_to_run.run_color != 0:
+ time.sleep(0.1)
+ # 6 and not 6\n6
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)
+ apply_function_inapp(self.window, testing_no_duplicates)
- def test_flow_right(self):
- """run block and next blocks when pressing right run."""
+ 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):
- block_to_run: OCBCodeBlock = self.blocks_to_run[
- self.titles.index("Test flow 5")
- ]
- block_output: OCBCodeBlock = self.blocks_to_run[
- self.titles.index("Test flow 8")
- ]
- block_to_not_run: OCBCodeBlock = self.blocks_to_run[
- self.titles.index("Test flow 4")
- ]
+ block_to_run: OCBCodeBlock = self.blocks_to_run[0]
+ block_to_not_run: OCBCodeBlock = self.blocks_to_run[1]
def run_block():
- block_to_run.run_right()
+ block_to_run.run_left()
msgQueue.run_lambda(run_block)
- time.sleep(1)
+ time.sleep((block_to_run.transmitting_duration / 1000) + 0.2)
+ while block_to_run.run_color != 0:
+ time.sleep(0.1)
- msgQueue.check_equal(block_output.stdout.strip(), "21")
+ msgQueue.check_equal(block_to_run.stdout.strip(), "6")
msgQueue.check_equal(block_to_not_run.stdout.strip(), "")
msgQueue.stop()
@@ -105,8 +94,12 @@ 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((block_to_run.transmitting_duration / 1000) + 0.2)
+ while block_to_run.run_color != 0:
+ time.sleep(0.1)
msgQueue.check_equal(block_to_run.stdout.strip(), "1")
msgQueue.stop()
@@ -126,9 +119,14 @@ def run_block():
block_to_run.run_right()
msgQueue.run_lambda(run_block)
- time.sleep(0.5)
+ time.sleep((block_to_run.transmitting_duration / 1000) + 0.2)
+ while block_to_run.run_color != 0:
+ time.sleep(0.1)
# Just check that it doesn't crash
msgQueue.stop()
apply_function_inapp(self.window, testing_run)
+
+ def test_finish(self):
+ self.window.close()
diff --git a/tests/integration/test_window.py b/tests/integration/test_window.py
index 1ab0ecee..06a084bd 100644
--- a/tests/integration/test_window.py
+++ b/tests/integration/test_window.py
@@ -13,20 +13,19 @@
class TestWindow:
-
@pytest.fixture(autouse=True)
def setup(self, mocker: MockerFixture):
- """ Setup reused variables. """
+ """Setup reused variables."""
self.window = OCBWindow()
- def test_window_close(self, qtbot):
- """ closes """
- self.window.close()
-
- def test_open_file(self):
- """ loads files """
+ 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()
diff --git a/tests/integration/utils.py b/tests/integration/utils.py
index 41d1ea44..53a5ab1c 100644
--- a/tests/integration/utils.py
+++ b/tests/integration/utils.py
@@ -5,14 +5,18 @@
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 +36,37 @@ 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"""
+ 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
@@ -39,11 +74,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()
@@ -53,5 +91,13 @@ 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()
- window.close()
diff --git a/tests/unit/scene/test_clipboard.py b/tests/unit/scene/test_clipboard.py
index 99739f20..d28b2087 100644
--- a/tests/unit/scene/test_clipboard.py
+++ b/tests/unit/scene/test_clipboard.py
@@ -15,7 +15,7 @@ class TestSerializeSelected:
"""SceneClipboard._serializeSelected"""
@pytest.fixture(autouse=True)
- def setup(self, mocker:MockerFixture):
+ def setup(self, mocker: MockerFixture):
self.scene = mocker.MagicMock()
self.view = mocker.MagicMock()
@@ -44,23 +44,23 @@ def setup(self, mocker:MockerFixture):
self.scene.sortedSelectedItems.return_value = self.blocks, self.edges
self.clipboard = SceneClipboard(self.scene)
- def test_serialize_selected_blocks(self, mocker:MockerFixture):
- """ should allow for blocks serialization."""
+ def test_serialize_selected_blocks(self, mocker: MockerFixture):
+ """should allow for blocks serialization."""
data = self.clipboard._serializeSelected()
- check.equal(data['blocks'], [block.serialize() for block in self.blocks])
+ check.equal(data["blocks"], [block.serialize() for block in self.blocks])
- def test_serialize_selected_edges(self, mocker:MockerFixture):
- """ should allow for edges serialization."""
+ def test_serialize_selected_edges(self, mocker: MockerFixture):
+ """should allow for edges serialization."""
data = self.clipboard._serializeSelected()
- check.equal(data['edges'], [edge.serialize() for edge in self.edges])
+ check.equal(data["edges"], [edge.serialize() for edge in self.edges])
- def test_serialize_partially_selected_edges(self, mocker:MockerFixture):
- """ should not allow for partially selected edges serialization."""
+ def test_serialize_partially_selected_edges(self, mocker: MockerFixture):
+ """should not allow for partially selected edges serialization."""
self.scene.sortedSelectedItems.return_value = self.blocks[0], self.edges
data = self.clipboard._serializeSelected()
- check.equal(data['edges'], [self.edges[0].serialize()])
+ check.equal(data["edges"], [self.edges[0].serialize()])
- def test_serialize_delete(self, mocker:MockerFixture):
- """ should allow for items deletion after serialization."""
+ def test_serialize_delete(self, mocker: MockerFixture):
+ """should allow for items deletion after serialization."""
self.clipboard._serializeSelected(delete=True)
check.is_true(self.view.deleteSelected.called)
diff --git a/tests/unit/scene/test_function_parsing.py b/tests/unit/scene/test_function_parsing.py
index d6e01cad..ec862ce4 100644
--- a/tests/unit/scene/test_function_parsing.py
+++ b/tests/unit/scene/test_function_parsing.py
@@ -4,12 +4,15 @@
from pytest_mock import MockerFixture
import pytest_check as check
-from opencodeblocks.graphics.function_parsing import (find_kwarg_index, run_cell,
- get_function_name,
- get_signature,
- extract_args,
- execute_function,
- find_kwarg_index)
+from opencodeblocks.graphics.function_parsing import (
+ find_kwarg_index,
+ run_cell,
+ get_function_name,
+ get_signature,
+ extract_args,
+ execute_function,
+ find_kwarg_index,
+)
class TestFunctionParsing:
@@ -17,72 +20,99 @@ class TestFunctionParsing:
"""Testing function_parsing functions"""
def test_run_cell(self, mocker: MockerFixture):
- """ Test run_cell """
- check.equal(run_cell("print(10)"), '10\n')
+ """Test run_cell"""
+ check.equal(run_cell("print(10)"), "10\n")
def test_get_function_name(self, mocker: MockerFixture):
- """ Test get_function_name """
- check.equal(get_function_name(
- "def function():\n return 'Hello'"), 'function')
- check.equal(get_function_name(
- "#Hello\ndef function():\n return 'Hello'\na = 10"), 'function')
- check.equal(get_function_name(
- "#Hello\ndef function(a,b=10):\n return 'Hello'\na = 10"), 'function')
+ """Test get_function_name"""
+ check.equal(get_function_name("def function():\n return 'Hello'"), "function")
+ check.equal(
+ get_function_name("#Hello\ndef function():\n return 'Hello'\na = 10"),
+ "function",
+ )
+ check.equal(
+ get_function_name(
+ "#Hello\ndef function(a,b=10):\n return 'Hello'\na = 10"
+ ),
+ "function",
+ )
def test_get_function_name_error(self, mocker: MockerFixture):
- """ Return ValueError if get_function_name has wrong input """
+ """Return ValueError if get_function_name has wrong input"""
with pytest.raises(ValueError):
get_function_name("")
get_function_name("#Hello")
get_function_name("def function")
def test_get_signature(self, mocker: MockerFixture):
- """ Test get_signature """
+ """Test get_signature"""
mocker.patch(
- 'opencodeblocks.graphics.function_parsing.run_cell', return_value="(a, b, c=10)\n")
- check.equal(get_signature(
- "def function(a,b, c=10):\n return None"), "(a, b, c=10)\n")
+ "opencodeblocks.graphics.function_parsing.run_cell",
+ return_value="(a, b, c=10)\n",
+ )
+ check.equal(
+ get_signature("def function(a,b, c=10):\n return None"), "(a, b, c=10)\n"
+ )
def test_find_kwarg_index(self, mocker: MockerFixture):
- """ Test find_kwarg_index """
- check.equal(find_kwarg_index(['a', 'b', 'c=10']), 2)
+ """Test find_kwarg_index"""
+ check.equal(find_kwarg_index(["a", "b", "c=10"]), 2)
check.equal(find_kwarg_index([]), 0)
def test_extract_args(self, mocker: MockerFixture):
- """ Test extract_args """
+ """Test extract_args"""
mocker.patch(
- 'opencodeblocks.graphics.function_parsing.get_signature', return_value="()\n")
+ "opencodeblocks.graphics.function_parsing.get_signature",
+ return_value="()\n",
+ )
mocker.patch(
- 'opencodeblocks.graphics.function_parsing.find_kwarg_index', return_value=0)
- check.equal(extract_args(
- "def function():\n return 'Hello'"), ([], []))
- mocker.patch('opencodeblocks.graphics.function_parsing.get_signature',
- return_value="(a,b,c = 10)\n")
+ "opencodeblocks.graphics.function_parsing.find_kwarg_index", return_value=0
+ )
+ check.equal(extract_args("def function():\n return 'Hello'"), ([], []))
mocker.patch(
- 'opencodeblocks.graphics.function_parsing.find_kwarg_index', return_value=2)
- check.equal(extract_args(
- "def function(a,b,c = 10):\n return 'Hello'"), (["a", "b"], ["c=10"]))
+ "opencodeblocks.graphics.function_parsing.get_signature",
+ return_value="(a,b,c = 10)\n",
+ )
+ mocker.patch(
+ "opencodeblocks.graphics.function_parsing.find_kwarg_index", return_value=2
+ )
+ check.equal(
+ extract_args("def function(a,b,c = 10):\n return 'Hello'"),
+ (["a", "b"], ["c=10"]),
+ )
def test_extract_args_empty(self, mocker: MockerFixture):
- """ Return a couple of empty lists if signature is empty """
- mocker.patch('opencodeblocks.graphics.function_parsing.get_signature',
- return_value="()\n")
+ """Return a couple of empty lists if signature is empty"""
+ mocker.patch(
+ "opencodeblocks.graphics.function_parsing.get_signature",
+ return_value="()\n",
+ )
mocker.patch(
- 'opencodeblocks.graphics.function_parsing.find_kwarg_index', return_value=None)
- check.equal(extract_args(
- "def function( ):\n return 'Hello'"), ([], []))
- mocker.patch('opencodeblocks.graphics.function_parsing.get_signature',
- return_value="()\n")
+ "opencodeblocks.graphics.function_parsing.find_kwarg_index",
+ return_value=None,
+ )
+ check.equal(extract_args("def function( ):\n return 'Hello'"), ([], []))
mocker.patch(
- 'opencodeblocks.graphics.function_parsing.find_kwarg_index', return_value=None)
- check.equal(extract_args(
- "def function():\n return 'Hello'"), ([], []))
+ "opencodeblocks.graphics.function_parsing.get_signature",
+ return_value="()\n",
+ )
+ mocker.patch(
+ "opencodeblocks.graphics.function_parsing.find_kwarg_index",
+ return_value=None,
+ )
+ check.equal(extract_args("def function():\n return 'Hello'"), ([], []))
def test_execute_function(self, mocker: MockerFixture):
- """ Test execute_function """
- mocker.patch('opencodeblocks.graphics.function_parsing.get_function_name',
- return_value="function")
+ """Test execute_function"""
+ mocker.patch(
+ "opencodeblocks.graphics.function_parsing.get_function_name",
+ return_value="function",
+ )
mocker.patch(
- 'opencodeblocks.graphics.function_parsing.run_cell', return_value="Out[1]: 25\n")
- check.equal(execute_function(
- "def function(a,b,c=10):\n return a+b+c", 10, 5), "Out[1]: 25\n")
+ "opencodeblocks.graphics.function_parsing.run_cell",
+ return_value="Out[1]: 25\n",
+ )
+ check.equal(
+ execute_function("def function(a,b,c=10):\n return a+b+c", 10, 5),
+ "Out[1]: 25\n",
+ )
diff --git a/tests/unit/scene/test_ipynb_conversion.py b/tests/unit/scene/test_ipynb_conversion.py
new file mode 100644
index 00000000..88d1a4cf
--- /dev/null
+++ b/tests/unit/scene/test_ipynb_conversion.py
@@ -0,0 +1,129 @@
+"""Unit tests for the conversion from and to ipynb."""
+
+from typing import OrderedDict
+from pytest_mock import MockerFixture
+import pytest_check as check
+import json
+
+from opencodeblocks.scene.from_ipynb_conversion import ipynb_to_ipyg, is_title
+from opencodeblocks.scene.ipynb_conversion_constants import BLOCK_TYPE_TO_NAME
+
+
+class TestIpynbConversion:
+
+ """Conversion from .ipynb"""
+
+ def test_empty_data(self, mocker: MockerFixture):
+ """should return empty ipyg graph for empty data."""
+ 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."""
+ file_path = "./tests/assets/empty.ipynb"
+ real_notebook_conversion_is_coherent(file_path)
+
+ def test_usual_notebook_data(self, mocker: MockerFixture):
+ """should return expected graph for a real usual notebook data."""
+ file_path = "./tests/assets/usual.ipynb"
+ real_notebook_conversion_is_coherent(file_path)
+
+ def test_complex_notebook_data(self, mocker: MockerFixture):
+ """should return expected graph for a real complex notebook data."""
+ file_path = "./tests/assets/complex.ipynb"
+ real_notebook_conversion_is_coherent(file_path)
+
+ def test_is_title(self, mocker: MockerFixture):
+ """should return True iff the given text can be used as a title for a block."""
+ check.equal(is_title(string_to_markdown_block("")), False)
+ check.equal(is_title(string_to_markdown_block("Data Preprocessing")), True)
+ check.equal(is_title(string_to_markdown_block("Étude de cas")), True)
+ check.equal(is_title(string_to_markdown_block("# Report")), False)
+ check.equal(
+ is_title(
+ string_to_markdown_block(
+ "This is a very very very very very very very very very very very very very very very very very very long explanation"
+ )
+ ),
+ False,
+ )
+ check.equal(is_title(string_to_markdown_block("New line \n Next line")), False)
+
+
+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
+ """
+ ipynb_data = load_json(file_path)
+ ipyg_data = ipynb_to_ipyg(ipynb_data, False)
+ check_conversion_coherence(ipynb_data, ipyg_data)
+
+
+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
+ 1. blocks and edges
+ 2. the right amount of code blocks and edges
+ 3. blocks and sockets with unique ids
+ 4. edges with existing ids
+ 5. code blocks that always have a source
+ 6. markdown blocks that always have text
+ """
+
+ # blocks and edges are present
+ check.equal("blocks" in ipyg_data and "edges" in ipyg_data, True)
+
+ # the amount of code blocks and edges is right
+ code_blocks_in_ipynb: int = 0
+ for cell in ipynb_data["cells"]:
+ if cell["cell_type"] == "code":
+ code_blocks_in_ipynb += 1
+ code_blocks_in_ipyg: int = 0
+ for block in ipyg_data["blocks"]:
+ if block["block_type"] == BLOCK_TYPE_TO_NAME["code"]:
+ code_blocks_in_ipyg += 1
+ check.equal(code_blocks_in_ipyg, code_blocks_in_ipynb)
+
+ # blocks and sockets have unique ids
+ block_id_set = set([])
+ socket_id_set = set([])
+ for block in ipyg_data["blocks"]:
+ if "id" in block:
+ check.equal(block["id"] in block_id_set, False)
+ block_id_set.add(block["id"])
+ if "sockets" in block:
+ for socket in block["sockets"]:
+ if "id" in socket:
+ check.equal(socket["id"] in socket_id_set, False)
+ socket_id_set.add(socket["id"])
+
+ # edges are between objects with existing ids
+ for edge in ipyg_data["edges"]:
+ check.equal(edge["source"]["block"] in block_id_set, True)
+ check.equal(edge["destination"]["block"] in block_id_set, True)
+ check.equal(edge["source"]["socket"] in socket_id_set, True)
+ check.equal(edge["destination"]["socket"] in socket_id_set, True)
+
+ # code blocks always have a source and markdown blocks always have a text
+ for block in ipyg_data["blocks"]:
+ if block["block_type"] == BLOCK_TYPE_TO_NAME["code"]:
+ check.equal("source" in block and type(block["source"]) == str, True)
+ if block["block_type"] == BLOCK_TYPE_TO_NAME["markdown"]:
+ check.equal("text" in block and type(block["text"]) == str, True)
+
+
+def load_json(file_path: str):
+ """Helper function that returns the ipynb data in a given file."""
+ with open(file_path, "r", encoding="utf-8") as file:
+ data = json.loads(file.read())
+ return data
+
+
+def string_to_markdown_block(string: str):
+ """Helper function that returns the ipyg data necessary for the is_title function to work."""
+ return {
+ "block_type": BLOCK_TYPE_TO_NAME["markdown"],
+ "text": string,
+ }