diff --git a/.github/workflows/pypi.yml b/.github/workflows/pypi.yml
new file mode 100644
index 00000000..17bb058b
--- /dev/null
+++ b/.github/workflows/pypi.yml
@@ -0,0 +1,31 @@
+name: PyPi
+
+on:
+ push:
+ tags:
+ - 'v*'
+ release:
+ types: [published]
+
+jobs:
+ deploy:
+
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/checkout@v2
+ - name: Set up Python
+ uses: actions/setup-python@v1
+ with:
+ python-version: '3.x'
+ - name: Install dependencies
+ run: |
+ python -m pip install --upgrade pip
+ pip install setuptools wheel twine
+ - name: Build and publish
+ env:
+ TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }}
+ TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }}
+ run: |
+ python setup.py sdist bdist_wheel
+ twine upload dist/*
\ No newline at end of file
diff --git a/.github/workflows/python-coverage.yml b/.github/workflows/python-coverage.yml
index 984cf783..306a87e3 100644
--- a/.github/workflows/python-coverage.yml
+++ b/.github/workflows/python-coverage.yml
@@ -1,6 +1,3 @@
-# Pyflow an open-source tool for modular visual programing in python
-# Copyright (C) 2021 Mathïs FEDERICO
-
name: Python coverage
on: ["push"]
diff --git a/.github/workflows/python-pylint.yml b/.github/workflows/python-pylint.yml
index 354e95a0..d8c98dbf 100644
--- a/.github/workflows/python-pylint.yml
+++ b/.github/workflows/python-pylint.yml
@@ -1,6 +1,3 @@
-# Pyflow an open-source tool for modular visual programing in python
-# Copyright (C) 2021 Mathïs FEDERICO
-
name: Pylint
on: ["push"]
diff --git a/.github/workflows/python-tests.yml b/.github/workflows/python-tests.yml
index abaee1c5..633a9d19 100644
--- a/.github/workflows/python-tests.yml
+++ b/.github/workflows/python-tests.yml
@@ -1,6 +1,3 @@
-# Pyflow an open-source tool for modular visual programing in python
-# Copyright (C) 2021 Mathïs FEDERICO
-
name: Pytest
on: ["push"]
diff --git a/.pylintrc b/.pylintrc
index a01545e3..0edfbd9b 100644
--- a/.pylintrc
+++ b/.pylintrc
@@ -3,18 +3,19 @@
# A comma-separated list of package or module names from where C extensions may
# be loaded. Extensions are loading into the active Python interpreter and may
# run arbitrary code.
-extension-pkg-whitelist=
+extension-pkg-whitelist=PyQt5,
# Specify a score threshold to be exceeded before program exits with error.
fail-under=10.0
# Add files or directories to the blacklist. They should be base names, not
# paths.
-ignore=CVS
+ignore=CVS,
+ tests
# Add files or directories matching the regex patterns to the blacklist. The
# regex matches against base names, not paths.
-ignore-patterns=test_*
+ignore-patterns=
# Python code to execute, usually for sys.path manipulation such as
# pygtk.require().
@@ -60,7 +61,8 @@ confidence=
# --enable=similarities". If you want to run only the classes checker, but have
# no Warning level messages displayed, use "--disable=all --enable=classes
# --disable=W".
-disable=inconsistent-return-statements,
+disable=bad-continuation,
+ inconsistent-return-statements,
unidiomatic-typecheck,
attribute-defined-outside-init,
pointless-statement,
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 90dc0fef..ad04f827 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -13,8 +13,8 @@ Feel free to fork the repository, implement your changes and create a merge requ
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`
@@ -34,29 +34,29 @@ black .
pytest --cov=pyflow --cov-report=html tests/unit
```
-We want to keep the *Pylint* score above *9.0*.
+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``).
+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 |
- | :beetle: `:beetle:` | When you fixed a bug |
- | :fire: `:fire:` | When you removed something |
- | :truck: `:truck:` | When you moved/renamed something |
- | :wrench: `:wrench:` | When you improved/refactored a small piece of code |
- | :hammer: `:hammer:` | When you improved/refactored a large piece of code |
- | :sparkles: `:sparkles:` | When you improved code quality (pylint, PEP, ...) |
- | :art: `:art:` | When you improved/added design assets |
- | :rocket: `:rocket:` | When you improved performance. |
- | :memo: `:memo:` | When you wrote documentation. |
- | :umbrella: `:umbrella:` | When you improved coverage |
- | :twisted_rightwards_arrows: `:twisted_rightwards_arrows:` | When you merge a branch |
+| Emoji | Description |
+| --------------------------------------------------------- | -------------------------------------------------- |
+| :tada: `:tada:` | When you add a cool new feature |
+| :beetle: `:beetle:` | When you fixed a bug |
+| :fire: `:fire:` | When you removed something |
+| :truck: `:truck:` | When you moved/renamed something |
+| :wrench: `:wrench:` | When you improved/refactored a small piece of code |
+| :hammer: `:hammer:` | When you improved/refactored a large piece of code |
+| :sparkles: `:sparkles:` | When you improved code quality (pylint, PEP, ...) |
+| :art: `:art:` | When you improved/added design assets |
+| :rocket: `:rocket:` | When you improved performance. |
+| :memo: `:memo:` | When you wrote documentation. |
+| :umbrella: `:umbrella:` | When you improved coverage |
+| :twisted_rightwards_arrows: `:twisted_rightwards_arrows:` | When you merge a branch |
This section was inspired by [This repository](https://github.com/schneegans/dynamic-badges-action).
@@ -70,6 +70,6 @@ create a new block type.
Version numbers will be assigned according to the [Semantic Versioning](https://semver.org/) scheme.
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.
+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.
diff --git a/MANIFEST.in b/MANIFEST.in
new file mode 100644
index 00000000..d87e2bbf
--- /dev/null
+++ b/MANIFEST.in
@@ -0,0 +1,6 @@
+include pyflow/themes/*
+include pyflow/blocks/blockfiles/*
+include pyflow/qss/*
+include PYPI_README.md
+include VERSION
+include requirements.txt
\ No newline at end of file
diff --git a/README.md b/README.md
index 09af31a5..f7eb7356 100644
--- a/README.md
+++ b/README.md
@@ -1,11 +1,11 @@
# 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)
-[![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)
-[![Total coverage Codacy Badge](https://app.codacy.com/project/badge/Coverage/ddd03302fd7c4849b452959753bc0939)](https://www.codacy.com/gh/MathisFederico/opencodeblocks/dashboard?utm_source=github.com&utm_medium=referral&utm_content=MathisFederico/opencodeblocks&utm_campaign=Badge_Coverage)
-[![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/PyFlow/actions/workflows/python-tests.yml/badge.svg?branch=master)](https://github.com/Bycelium/PyFlow/actions/workflows/python-tests.yml)
+[![Codacy Badge](https://app.codacy.com/project/badge/Grade/9874915d70e440418447f371c4bd5061)](https://www.codacy.com/gh/Bycelium/PyFlow/dashboard?utm_source=github.com&utm_medium=referral&utm_content=Bycelium/PyFlow&utm_campaign=Badge_Grade)
+[![Pylint badge](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FMathisFederico%2F00ce73155619a4544884ca6d251954b3%2Fraw%2Fopencodeblocks_pylint_badge.json)](https://github.com/Bycelium/PyFlow/actions/workflows/python-pylint.yml)
+[![Codacy Badge](https://app.codacy.com/project/badge/Coverage/9874915d70e440418447f371c4bd5061)](https://www.codacy.com/gh/Bycelium/PyFlow/dashboard?utm_source=github.com&utm_medium=referral&utm_content=Bycelium/PyFlow&utm_campaign=Badge_Coverage)
+[![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/Bycelium/PyFlow/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/Bycelium/PyFlow/actions/workflows/python-coverage.yml)
[![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)
@@ -21,47 +21,43 @@ Join our [Discord](https://discord.gg/xZq8Tp4srd) to beta-test features, share y
## Features
-- Create blocks of code in which you can edit and run Python code
+- Create blocks of code in which you can edit and run Python code
-
+
-- Move and resize blocks on an infinite 2D plane
+- Move and resize blocks on an infinite 2D plane
-
+
-- Link blocks to highlight dependencies, Pyflow will then automatically run your blocks in the correct order
+- Link blocks to highlight dependencies, Pyflow will then automatically run your blocks in the correct order
-
+
-- Convert your Jupyter notebooks to Pyflow graphs and vice versa
+- Convert your Jupyter notebooks to Pyflow graphs and vice versa
-
+
## Installation
Make sure you have Python 3 installed. You can download it from [here](https://www.python.org/downloads/)
-Clone the current repo:
+### Install PyFlow
-```bash
-git clone https://github.com/MathisFederico/Pyflow/
-```
-
-Install the dependencies
+Using pip:
```bash
-pip install -r requirements.txt
+pip install byc-pyflow
```
-Run !
+### Run PyFlow
```bash
python -m pyflow
diff --git a/VERSION b/VERSION
new file mode 100644
index 00000000..537aabf7
--- /dev/null
+++ b/VERSION
@@ -0,0 +1 @@
+1.0.0-beta
\ No newline at end of file
diff --git a/coverage_score.py b/coverage_score.py
index d9ffc243..9596e359 100644
--- a/coverage_score.py
+++ b/coverage_score.py
@@ -1,7 +1,7 @@
# Pyflow an open-source tool for modular visual programing in python
-# Copyright (C) 2021 Mathïs FEDERICO
+# Copyright (C) 2021-2022 Bycelium
-""" Module to get coverage score. """
+""" Module to get coverage score."""
import sys
from xml.dom import minidom
diff --git a/examples/linear_classifier.ipyg b/examples/linear_classifier.ipyg
index a02f6f4a..4e3eb925 100644
--- a/examples/linear_classifier.ipyg
+++ b/examples/linear_classifier.ipyg
@@ -4,7 +4,7 @@
{
"id": 2034638878464,
"title": "",
- "block_type": "OCBMarkdownBlock",
+ "block_type": "MarkdownBlock",
"splitter_pos": [
0,
200
@@ -28,7 +28,7 @@
{
"id": 2034686482320,
"title": "Show the data",
- "block_type": "OCBCodeBlock",
+ "block_type": "CodeBlock",
"splitter_pos": [
0,
272
@@ -77,12 +77,12 @@
}
],
"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"
+ "stdout": "iVBORw0KGgoAAAANSUhEUgAAAXAAAAD4CAYAAAD1jb0+AAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjQuMywgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/MnkTPAAAACXBIWXMAAAsTAAALEwEAmpwYAAAdkklEQVR4nO3dfXyU5Z3v8c+PECCgEikpC0EaixZWpYCN2oKrgg+IuJbaHm3P6urWltq12/qwWIJW0YqgaNU9p8cjra2PrboWY1etSAHr9qygwSCgiA+ArgEhVmJFIoTwO3/MJGYmM5lJmHvuefi+Xy9e5L7mHuc3vvDrxXVfD+buiIhI/ukVdgEiItIzCnARkTylABcRyVMKcBGRPKUAFxHJU72z+WGDBw/2qqqqbH6kiEjeW7Vq1fvuXhHfntUAr6qqoq6uLpsfKSKS98zs7UTtaQ2hmNlmM1trZqvNrC7aNsfMGqJtq83sjEwWLCIiXetOD3ySu78f13abu9+SyYJERCQ9eogpIpKn0g1wB54xs1VmNqND+w/MbI2Z/crMDk70RjObYWZ1ZlbX2Ni43wWLiEhEugF+vLsfDUwFLjGzE4A7gZHAOGArcGuiN7r7QnevdvfqiopOD1FFRKSH0hoDd/eG6O/bzewx4Fh3f67tdTP7BfBEMCWKiOS22voGFizewJamZoaVlzFzyiimj68M/HNT9sDNbICZHdj2M3AasM7Mhna47WvAumBKFBHJXbX1DdQsWktDUzMONDQ1U7NoLbX1DYF/djo98CHAY2bWdv9v3P1pM7vfzMYRGR/fDHwvqCJFRHLVnN+/QnNLa0xbc0srCxZvCLwXnjLA3X0jMDZB+/mBVCQikidq6xtoam5J+NqWpubAPz+rKzFFRArJgsUbkr42rLwMCHZ8XAEuItJDXfWyZ04Z1T4+3jbE0jY+DmQkxLWQR0Skh9p62fEO7l/K9PGVLFi8Ien4eCYowEVEemjmlFGUlZbEtJWVlnDt3x8JJO+hZ2p8XAEuItJD08dXMu/sMVSWl2FAZXkZ884e0z48MnRgv4TvS9Zz7y6NgYuI7Ifp4ysTjmc/ve49tnz4Saf2stISZk4ZlZHPVoCLiGTQR5+0MGbOM+3Xn68YwCd7Wtn64SeahSIikqv+bekb/GzJ6+3Xiy89gVF/c2Bgn6cAFxHZT39+433Ou3tl+/V3jj+Uq888IvDPVYCLSNHJ1OIad+fQmqdi2l76yakMGtAnU6V2SQEuIkUlU4trzrnreV7Y9EH79YA+Jbxy/emZLTYFBbiIFJWuFtekE+CNH+3mmLl/jGl7+drTGFhWmtE606EAF5GC1nG4pLx/KTt29XzzqapZT8ZcTz3qb7jzvC9lpM6eUICLSMGKHy5JFt7Q9eKaZzds58JfvxjTtmneGUS32Q6NAlxEClai4ZJkJo1OfORjfK/73741nrPGDtvv2jJBAS4iBas7e44sfy320PWf1K7j/hVvx7Rtnj8tI3VlSloBbmabgY+AVmCvu1eb2SDgYaCKyIk857j7jmDKFBHpntr6BnqZ0eqe1v1tYd+8p5W/vebpmNf+88pJHDKof8Zr3F/d6YFPcvf3O1zPApa6+3wzmxW9/nFGqxMR6YG2se90wxsiY+Cfr3mSfR3eUvWZ/jw7c1IAFWbG/gyhfBU4KfrzvcCzKMBFJAckG/s2g7LevdjVsi+mvW/vXjTEDbe8OXcqvUtye8PWdKtz4BkzW2VmM6JtQ9x9a/Tn94gcftyJmc0wszozq2tsbEx0i4hIRiUd+3Z49adTuf3cce1bwALs3vtpoM+cMorN86flfHhD+j3w4929wcw+Cywxs9c6vujubmYJ/67i7guBhQDV1dXp/31GRKSHhpWXdepRt7VDZMVl/Ts7uPf53H5ImUpaAe7uDdHft5vZY8CxwDYzG+ruW81sKLA9wDpFRID09jGZOWVUzPxv+HQf7tZ9zsjZsfuX1F4ykXGHlGej/IxK+XcEMxtgZge2/QycBqwDfg9cEL3tAuDxoIoUEYFPH042NDXjfLqPSW19Q8x9yU7KufTh1Z3Ce/P8aXkZ3pBeD3wI8Fh0xVFv4Dfu/rSZvQg8YmYXAW8D5wRXpohI8n1MrnjkZSB2M6qOJ+W8se0jTr3tuZj3rb7mVMr7Z2fXwKCkDHB33wiMTdD+F+DkIIoSEUkk2cPJVvekOwrGr6T8fMUAll1xUiD1ZZtWYopIXki1MCd+R8ErH32ZR+rejbkn3x5SpqIAF5Gcl+7CnLYeenyv++ITRzJr6ujA6guLAlxEcl66m1I5ncO70HrdHSnARSSn1dY3JJzTncqif57A0SMODqCi3KEAF5Gc1TZ0kkxJkjHxQu51d6QAF5Gc1dXQSWmJ0dIaG95vzJ1KaR4sgc8UBbiIZF26p8J3tZ93x/AeclBfVs4+JZBac5kCXESyqjunwifb06SjYhkuSaR4/q4hIjmhq1Ph482cMoqy0pKE/5yrp/1tUYc3qAcuIgGLHy5J1qNO1D59fCWXPry6U3uxB3cb9cBFJDCJNp/q6hz3q2s/nXHy4uYPOs3pfmH2yQrvDtQDF5HAJBou6Wot5YMr3qH6c4PU606TAlxEAtOdU+EhEu7x4a3gTk5DKCISmLYTcOJ1NYzS5rwvj1B4p6AAF5HAJJpFUtrL6NWr6wjfPH8aN0wfE2RpBUFDKCISmLZ53R1noezas5cdu1oS3t+3dy9u+voXs1liXks7wM2sBKgDGtz9TDO7BzgR+DB6y4XuvjrjFYpIXut4Mg7AoXEzSzq66etfTLgiUxLrTg/8R8B64KAObTPd/dHMliQi+ayrZfLn/XJl0lkoleVlCu9uSivAzWw4MA2YC1weaEUikre6WiafaGpgm7YT46V70n2IeTtwJbAvrn2uma0xs9vMrG+iN5rZDDOrM7O6xsbG/ShVRHJdsmXy8eF9+7njOp0Yr95396XsgZvZmcB2d19lZid1eKkGeA/oAywEfgxcH/9+d18YfZ3q6uquz0MSkbyWat73hROqmHPWkUDnjauk+9IZQpkInGVmZwD9gIPM7AF3Py/6+m4z+zXwr0EVKSL5oau9TjSnO/NSDqG4e427D3f3KuCbwDJ3P8/MhgKYmQHTgXVBFioiuW/sIQM7tfXt3Yvbzx2X/WKKwP7MA3/QzCqILKpaDVyckYpEJC/FbzwFkfHtZIc1yP7rVoC7+7PAs9GfJwdQj4jkmUTBreGS7NBSehHpEXfvFN5mCu9s0lJ6Eek29bpzgwJcRNK2cuNfOHfhipi2X/xjNaceMSSkioqbAlxE0qJed+5RgItIlybOX9ZpbvdbN55BSYotYSV4CnARSUq97tymABeRThTc+UHTCEWk3fs7d3cK7wsnVCm8c5R64CIFpqv9uLuiXnf+MffsbRBYXV3tdXV1Wfs8kWITvx93m/KyUuacdWTCIJ/31Hruem5jTNsLV53MZw/sF2itkj4zW+Xu1fHt6oGLFJBE+3EDNDW3ULNoLXVvf8Dy1xrbe+eJdg5Urzt/KMBFCkhX+3E3t7Ty4Ip32o80iw9vBXf+0UNMkQIyrLysy9cTDZj20v4leUsBLlJAZk4ZRVlpSbfek8XHYJJhGkIRKSBtDymv+49X2LGrJa33pOq1S+5SD1ykwEwfX0n9NaeldQqOToPPb2n3wM2sBKgDGtz9TDM7FHgI+AywCjjf3fcEU6aIdMdxN/6RbX/dHdO2ad4ZPL56S4/miEtu6s4Qyo+A9cBB0eubgNvc/SEz+7/ARcCdGa5PRLqpqwU508dXKrALSFoBbmbDgWnAXODy6EHGk4H/Gb3lXmAOCnCRjOjJakqtpCw+6fbAbweuBA6MXn8GaHL3vdHrd4GEf7rMbAYwA2DEiBE9LlSkWMSvpmxoaqZm0VqAhCG+7a+fcNyNS2PaZk0dzcUnjgy+WAlVygA3szOB7e6+ysxO6u4HuPtCYCFEltJ39/0ixSbRasrmllYWLN7QKcDV6y5u6fTAJwJnmdkZQD8iY+B3AOVm1jvaCx8ONARXpkjxSLaasmP7DU+8yi//vCnm9ZevOY2B/UsDrU1yS8pphO5e4+7D3b0K+CawzN3/AVgOfCN62wXA44FVKVJEks3LbmuvmvVkp/DePH+awrsI7c9Cnh8DD5nZDUA9cHdmShIpLvEPLCeNruB3qxpihlHKSktoaGruNGSi4ZLipu1kRUKUaPvXstISjh4xkBUbd9DqTi9gX9z7jqk6mH+/eEJWa5XwaDtZkRyU7IHlf731QfvGU/HhrV63tNFSepEQJXtgmejvxRUH9FV4SwwFuEiIurOR1Ps7d6e+SYqKAlwkRN3Z/lW7Bko8jYGLhKhtYc6lD6/u8j7tGiiJKMBFQpRsJWVPT5aX4qIAFwlQsiDe0tTMhPnLYu695X+M5RtfGg5o10BJj+aBiwQk2RzvRKfGa3aJdEXzwEWyLNkc745evX4K/fvoP0PpGf3JEQlIsjnebdTrlv2laYQiAUk27a+yvEzhLRmhABcJwO69rTQk6IFrOqBkkoZQRHoo2QyTRFMDDTQdUDJOs1BEeiDRDJPSEqOlNfa/p2cuO4EvDDkw/u0i3aJZKCIZlGiGSXx4a5xbgqYAF+mBrmaYKLglW1I+xDSzfmb2gpm9bGavmNl10fZ7zGyTma2O/hoXeLUiOaKrGSYi2ZJOD3w3MNndd5pZKfBnM/tD9LWZ7v5ocOWJ5IaODyyTPTXSDBPJtpQB7pGnnDujl6XRX9l78ikSskQPLNsM6t+HHbv2aIaJhCKtMXAzKwFWAYcBP3f3lWb2fWCumV0DLAVmubt2nJeCk+iBJUSGS/7frMkhVCQSkdZCHndvdfdxwHDgWDM7CqgBRgPHAIOInFLfiZnNMLM6M6trbGzMTNUiWTLvD+sTLsiB1EvlRYLWrZWY7t4ELAdOd/etHrEb+DVwbJL3LHT3anevrqio2O+CRbKlataT3PWnjUlf1wk5EraUQyhmVgG0uHuTmZUBpwI3mdlQd99qZgZMB9YFW6pIdiRaSRm/DaweWEouSGcMfChwb3QcvBfwiLs/YWbLouFuwGrg4uDKFAneJy2tjP7J0zFtP5x8GJefNkon5EhO0lJ6EZIfbSaSC7SUXiSBxa+8x/fuXxXT9nzNZIYO1Pi25D4FuBQt9bol3ynApehMmLeULR9+EtOm4JZ8pACXouHuHFrzVEzb+BHlPPbPE0OqSGT/KMClKGi4RAqRAlwK2sbGnUy+9U8xbb/57nFMGDk4pIpEMkcBLgVLvW4pdApwKThX167lgRXvxLS9OXcqvUt0hrcUFgW4FBT1uqWYKMClICi4pRgpwCWvfbx7L0deuzimrWbqaL534siQKhLJHgW45C31uqXYKcAl79TWN3Dpw6tj2l686hQqDuwbTkEiIVGAS15Rr1vkUwpwyQtHXbuYnbv3xrQpuKXYKcAlVKkOSki0f8kJX6jgvm8nPMFPpKgowCU0tfUN1Cxa235UWUNTMzWL1gIwfXylhktEUkjnTMx+wHNA3+j9j7r7tWZ2KPAQ8BlgFXC+u+8JslgpLAsWb4g5ZxKguaWVG59a3+kh5e++P4Evfe7gLFYnkvvSWVu8G5js7mOBccDpZvZl4CbgNnc/DNgBXBRYlVKQtjQ1J2zf/tHumOvN86cpvEUSSBngHrEzelka/eXAZODRaPu9RE6mF0motr6BifOXceisJ5k4fxm19Q0MK099bFlZaQm19Q1ZqFAk/6S1u4+ZlZjZamA7sAR4C2hy97ZpAe8CCY/oNrMZZlZnZnWNjY0ZKFnyTdtYd0NTM86nY92TRldQVlrS5XubW1pZsHhDdgoVyTNpPcR091ZgnJmVA48Bo9P9AHdfCCyEyKn0PahR8kSyGSXJxrrjdwxMJtlQi0ix69YsFHdvMrPlwFeAcjPrHe2FDwf099wi1tWMkv0N4HSGWkSKUcohFDOriPa8MbMy4FRgPbAc+Eb0tguAxwOqUfJAsl72gsUbUgZwZRevl5WWMHPKqIzUKFJo0hkDHwosN7M1wIvAEnd/AvgxcLmZvUlkKuHdwZUpuS5ZL3tLUzMzp4yiT4LDFOafPYbN86cxc8qohGPh5WWlzDt7TMzCHhH5VMohFHdfA4xP0L4R0HI4ASLDHA0JQnxYeVmnOd0At587rj2Y237vakWmiHRm7tl7rlhdXe11dXVZ+zzJnvgxcIBeBvvi/nhpJaVI95nZKnevjm/XUnrJiI696LaeeMfw1iELIpmnAJeMmT6+MuFwiXrdIsFQgEtGvLl9J6f87E8xbf955SQOGdQ/pIpECp8CXPabdg0UCYcCXHrs5qdf4/88+1ZM26Z5Z2BmIVUkUlwU4NIj8b3u4w8bzAPfOS6kakSKkwJcgNQn47TRcIlI7lCAS8qTcQA+bG5h7HXPxLzv3m8fy4lfqMhusSLSTgEuXe5joqPNRHKXAlyS7mPS0NTcKbzXX386ZX263sNbRLIjrQMdpLClu13r5vnTFN4iOUQBLkl3A2yzef40DZmI5CANoQjTx1eyz53LH3k5pl37l4jkNgW46CGlSJ5SgBextxp3cvKtsfuXrJx9MkMO6hdSRSLSHSkD3MwOAe4DhgAOLHT3O8xsDvBdoO2o+dnu/lRQhUpmqdctkv/S6YHvBa5w95fM7EBglZktib52m7vfElx5kmk/X/4mCxZviGnT/iUi+SmdI9W2AlujP39kZusBnXWVh+J73SccPpj7LtL+JSL5qlvTCM2sisj5mCujTT8wszVm9iszOzjJe2aYWZ2Z1TU2Nia6RQJ2+FVPJRwyeXHzDmrrG0KoSEQyIe0AN7MDgN8Bl7r7X4E7gZHAOCI99FsTvc/dF7p7tbtXV1Ro34xs+nBXC1WznqSlNfG5p23L5UUkP6U1C8XMSomE94PuvgjA3bd1eP0XwBOBVCg9kqjHnUiyZfQikvtS9sAt8nTrbmC9u/+sQ/vQDrd9DViX+fKkO2rrGzj6p0s6hffrN0ylMsly+XSX0YtI7klnCGUicD4w2cxWR3+dAdxsZmvNbA0wCbgsyEKla7X1DVz68Go++HhPe5sZ3H7uOPr07pVwuXxZaQkzp4zKdqkikiHpzEL5M5BojpnmfOeIn9Su4/4Vb3dqd6d9S9i2fb3TObRBRPKDVmLmsdZ9zsjZXf9/tOMYd8cgF5H8pwDPU+k+pNQYt0jh0nayeWbz+x93Cu+XrzmN288dpzFukSKjHngeiQ/ukRUDWHrFSQAa4xYpQgrwPHD/irf5SW3sLM1EG09pjFukuCjAc1x8r/u6s47kgglV4RQjIjlFAR6i2vqGpEMep932J17ftjPmfm33KiIdKcBDUlvfQM2itTS3tAKRE+BrFq1l1569zH4sdrhk+b+exKGDB4RRpojkMAV4SBYs3tAe3m2aW1o7hbd63SKSjAI8JKk2kXpz7lR6l2iWp4gkp4QISbIFNv37lLB5/jSFt4ikpJQIyfgR5Z3aykpLuPFrY7JfjIjkJQ2hZNne1n0cdtUfOrVXauGNiHSTAjyLTr71Wd5q/DimTQ8pRaSnFOBZ0NDUzMT5y2LaXr1+Cv376F+/iPScEiQDulqQE7+S8lvHjmDe2RrnFpH9lzLAzewQ4D5gCODAQne/w8wGAQ8DVcBm4Bx33xFcqbkp2YKcTe9/zB1L34i5V8MlIpJJ6cxC2Qtc4e5HAF8GLjGzI4BZwFJ3PxxYGr0uOskW5MSHd2V5GbX1DdksTUQKXMoAd/et7v5S9OePgPVAJfBV4N7obfcC0wOqMad1tSCn4zl0bT1zhbiIZEq35oGbWRUwHlgJDHH3rdGX3iMyxJLoPTPMrM7M6hobG/en1pyUbEFOLyLjTR01t7SyYPGGwGsSkeKQd2QHA74BL3f2vHV9zd6dzXrW9ttDdq929uqKiYr+KzUUH9O38GKGstIR9Se5PtYReRCRdaQW4mZUSCe8H3X1RtHmbmQ2Nvj4U2B5MiblpY+NOqmY9yYZtH8W0V5aXMe/sMVQm6ZnrjEoRyZR0ZqEYcDew3t1/1uGl3wMXAPOjvz8eSIU5KH5q4D3/dAwnjfpsp/s6zk4BnVEpIpmVzjzwicD5wFozWx1tm00kuB8xs4uAt4FzAqkwhzy5ZiuX/Oal9msz2DQv8dRAnVEpIkGzyPB1dlRXV3tdXV3WPi9TWvc5I2c/FdP2X7MmazhERLLCzFa5e3V8u1ZipnB17VoeWPFO+/Xfjx3G//rW+BArEhGJUIAn0fjRbo6Z+8eYttdvmEqf3tqBV0RygwI8gS/9dAl/+XhP+/XN3/gi51QfEmJFIiKdKcA7eGHTB5xz1/Mxbdq/RERylQIccHcOrYl9SPnUD/+OI4YdFFJFIiKpFX2A3/nsW9z09Gvt12MqB/If/3J8iBWJiKSnaAP84917OfLaxTFta+acxkH9SkOqSESke4oywM+563le2PRB+/Vlp3yBH51yeIgViYh0X8EGeKJTco4cdhCn3vZczH2b5p1BZLcAEZH8UpABnuiUnEsfXh1zz4PfOY6Jhw0OoToRkcwoyABPdEpOm/59Snj1+tOzXJGISOYV5LLCrvbcVniLSKEoyAA/eECfhO3J9ugWEclHBTWEsnP3XsZe9wyt+zrvsKi9uEWk0BRMD/zny9/kqGsXt4f3zNNGUVlehvHpKTnai1tECkne98D/+4Nd/N3Ny9uvL5xQxZyzjgTgksmHhVWWiEjg0jlS7VfAmcB2dz8q2jYH+C7Qdsz8bHd/KvE/IRjuzvcfeImnX3mvva3u6lMYfEDfbJYhIhKadHrg9wD/G7gvrv02d78l4xWlYeXGv3DuwhXt1zd9fQznHjMijFJEREKTMsDd/Tkzq8pCLQl1XFE5dGA/Pt7TyofNLQAMP7iMpVecSN/eJWGVJyISmv0ZA/+Bmf0jUAdc4e47Et1kZjOAGQAjRnSvlxy/onLLh5+0v/bvF3+FY6oG9axyEZEC0NNZKHcCI4FxwFbg1mQ3uvtCd6929+qKiopufUiyFZXDBvZTeItI0etRgLv7Nndvdfd9wC+AYzNbVkSyFZVbO/TERUSKVY8C3MyGdrj8GrAuM+XEGpZk5WSydhGRYpIywM3st8DzwCgze9fMLgJuNrO1ZrYGmARcFkRxM6eMoqw09gGlVlSKiESkMwvlWwma7w6glk7aVk7G7+utFZUiInmwEnP6+EoFtohIAgWzF4qISLFRgIuI5CkFuIhInlKAi4jkKQW4iEieMvfOp9cE9mFmjcDbWfvA7hkMvB92ESHS9y/e71/M3x3y4/t/zt077UWS1QDPZWZW5+7VYdcRFn3/4v3+xfzdIb+/v4ZQRETylAJcRCRPKcA/tTDsAkKm71+8ivm7Qx5/f42Bi4jkKfXARUTylAJcRCRPKcABMysxs3ozeyLsWrLNzMrN7FEze83M1pvZV8KuKZvM7DIze8XM1pnZb82sX9g1BcnMfmVm281sXYe2QWa2xMzeiP5+cJg1BinJ918Q/fO/xsweM7PyEEvsFgV4xI+A9WEXEZI7gKfdfTQwliL692BmlcAPgWp3PwooAb4ZblWBuwc4Pa5tFrDU3Q8HlkavC9U9dP7+S4Cj3P2LwOtATbaL6qmiD3AzGw5MA34Zdi3ZZmYDgROIHtDh7nvcvSnUorKvN1BmZr2B/sCWkOsJlLs/B3wQ1/xV4N7oz/cC07NZUzYl+v7u/oy7741ergCGZ72wHir6AAduB64E9oVcRxgOBRqBX0eHkH5pZgPCLipb3L0BuAV4B9gKfOjuz4RbVSiGuPvW6M/vAUPCLCZk3wb+EHYR6SrqADezM4Ht7r4q7FpC0hs4GrjT3ccDH1PYf32OER3r/SqR/5ENAwaY2XnhVhUuj8wrLsq5xWZ2FbAXeDDsWtJV1AEOTATOMrPNwEPAZDN7INySsupd4F13Xxm9fpRIoBeLU4BN7t7o7i3AImBCyDWFYZuZDQWI/r495HqyzswuBM4E/sHzaHFMUQe4u9e4+3B3ryLy8GqZuxdND8zd3wP+28xGRZtOBl4NsaRsewf4spn1NzMj8v2L5iFuB78HLoj+fAHweIi1ZJ2ZnU5kGPUsd98Vdj3dkfOHGkvg/gV40Mz6ABuBfwq5nqxx95Vm9ijwEpG/OteTx8uq02FmvwVOAgab2bvAtcB84BEzu4jIds/nhFdhsJJ8/xqgL7Ak8v9xVrj7xaEV2Q1aSi8ikqeKeghFRCSfKcBFRPKUAlxEJE8pwEVE8pQCXEQkTynARUTylAJcRCRP/X/WlkqiYtOsfQAAAABJRU5ErkJggg==\n"
},
{
"id": 2034723533728,
"title": "Generate some data to plot",
- "block_type": "OCBCodeBlock",
+ "block_type": "CodeBlock",
"splitter_pos": [
189,
85
@@ -136,7 +136,7 @@
{
"id": 2034723677808,
"title": "Slider",
- "block_type": "OCBSliderBlock",
+ "block_type": "SliderBlock",
"splitter_pos": [],
"position": [
-890.625,
@@ -187,7 +187,7 @@
{
"id": 2034723714816,
"title": "Slider",
- "block_type": "OCBSliderBlock",
+ "block_type": "SliderBlock",
"splitter_pos": [],
"position": [
-901.1874999999999,
@@ -238,7 +238,7 @@
{
"id": 2034879162976,
"title": "Regression",
- "block_type": "OCBCodeBlock",
+ "block_type": "CodeBlock",
"splitter_pos": [
0,
276
@@ -292,7 +292,7 @@
{
"id": 2034879286288,
"title": "Show user input",
- "block_type": "OCBCodeBlock",
+ "block_type": "CodeBlock",
"splitter_pos": [
0,
220
@@ -346,7 +346,7 @@
{
"id": 2034886210608,
"title": "Create a new linear model",
- "block_type": "OCBCodeBlock",
+ "block_type": "CodeBlock",
"splitter_pos": [
90,
85
@@ -400,7 +400,7 @@
{
"id": 2136886539168,
"title": "Show user input",
- "block_type": "OCBCodeBlock",
+ "block_type": "CodeBlock",
"splitter_pos": [
0,
278
diff --git a/examples/mnist.ipyg b/examples/mnist.ipyg
index d8fd5740..a32f151a 100644
--- a/examples/mnist.ipyg
+++ b/examples/mnist.ipyg
@@ -4,7 +4,7 @@
{
"id": 2039122444152,
"title": "Load MNIST dataset",
- "block_type": "OCBCodeBlock",
+ "block_type": "CodeBlock",
"splitter_pos": [
131,
0
@@ -58,7 +58,7 @@
{
"id": 2039123153688,
"title": "Evaluation",
- "block_type": "OCBCodeBlock",
+ "block_type": "CodeBlock",
"splitter_pos": [
75,
75
@@ -112,7 +112,7 @@
{
"id": 2039123177336,
"title": "Prediction example",
- "block_type": "OCBCodeBlock",
+ "block_type": "CodeBlock",
"splitter_pos": [
0,
281
@@ -166,7 +166,7 @@
{
"id": 2039123183800,
"title": "Training",
- "block_type": "OCBCodeBlock",
+ "block_type": "CodeBlock",
"splitter_pos": [
85,
229
@@ -220,7 +220,7 @@
{
"id": 2039123243512,
"title": "Build Keras CNN",
- "block_type": "OCBCodeBlock",
+ "block_type": "CodeBlock",
"splitter_pos": [
253,
0
@@ -274,7 +274,7 @@
{
"id": 2039171273928,
"title": "Plot image example",
- "block_type": "OCBCodeBlock",
+ "block_type": "CodeBlock",
"splitter_pos": [
0,
277
@@ -328,7 +328,7 @@
{
"id": 2039171312808,
"title": "Normalize dataset",
- "block_type": "OCBCodeBlock",
+ "block_type": "CodeBlock",
"splitter_pos": [
73,
73
diff --git a/pyflow/__init__.py b/pyflow/__init__.py
index 8fdd529a..59068cb4 100644
--- a/pyflow/__init__.py
+++ b/pyflow/__init__.py
@@ -1,9 +1,9 @@
# -*- coding: utf-8 -*-
# Pyflow an open-source tool for modular visual programing in python
-# Copyright (C) 2021 Mathïs FEDERICO
+# Copyright (C) 2021-2022 Bycelium
""" Pyflow: An open-source tool for modular visual programing in python """
__appname__ = "Pyflow"
__author__ = "Mathïs Fédérico"
-__version__ = "0.0.1"
+__version__ = "1.0.0-beta"
diff --git a/pyflow/__main__.py b/pyflow/__main__.py
index 7d13e2c8..01401c4b 100644
--- a/pyflow/__main__.py
+++ b/pyflow/__main__.py
@@ -1,10 +1,8 @@
# Pyflow an open-source tool for modular visual programing in python
-# Copyright (C) 2021 Mathïs FEDERICO
+# Copyright (C) 2021-2022 Bycelium
# pylint:disable=wrong-import-position
-"""
-Pyflow main module, run this to launch Pyflow
-"""
+""" Pyflow main module. """
import os
import sys
@@ -13,19 +11,20 @@
if os.name == "nt": # If on windows
asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
-from qtpy.QtWidgets import QApplication
-from pyflow.graphics.window import OCBWindow
+from PyQt5.QtWidgets import QApplication
+from pyflow.graphics.window import Window
+from pyflow import __version__
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", ".."))
if __name__ == "__main__":
app = QApplication(sys.argv)
app.setStyle("Fusion")
- wnd = OCBWindow()
+ wnd = Window()
if len(sys.argv) > 1:
wnd.createNewMdiChild(sys.argv[1])
- wnd.setWindowTitle("Pyflow Beta v0.1")
+ wnd.setWindowTitle(f"Pyflow {__version__}")
wnd.show()
sys.exit(app.exec_())
diff --git a/pyflow/blocks/__init__.py b/pyflow/blocks/__init__.py
index a22024d9..5d646f21 100644
--- a/pyflow/blocks/__init__.py
+++ b/pyflow/blocks/__init__.py
@@ -1,10 +1,10 @@
# Pyflow an open-source tool for modular visual programing in python
-# Copyright (C) 2021 Mathïs FEDERICO
+# Copyright (C) 2021-2022 Bycelium
-""" Module for the OCB Blocks of different types. """
+""" Module for the Blocks of different types. """
-from pyflow.blocks.sliderblock import OCBSliderBlock
-from pyflow.blocks.codeblock import OCBCodeBlock
-from pyflow.blocks.markdownblock import OCBMarkdownBlock
-from pyflow.blocks.drawingblock import OCBDrawingBlock
-from pyflow.blocks.containerblock import OCBContainerBlock
+from pyflow.blocks.sliderblock import SliderBlock
+from pyflow.blocks.codeblock import CodeBlock
+from pyflow.blocks.markdownblock import MarkdownBlock
+from pyflow.blocks.drawingblock import DrawingBlock
+from pyflow.blocks.containerblock import ContainerBlock
diff --git a/pyflow/blocks/block.py b/pyflow/blocks/block.py
index 917b0b4f..49e89d3f 100644
--- a/pyflow/blocks/block.py
+++ b/pyflow/blocks/block.py
@@ -1,8 +1,8 @@
# Pyflow an open-source tool for modular visual programing in python
-# Copyright (C) 2021 Mathïs FEDERICO
+# Copyright (C) 2021-2022 Bycelium
# pylint:disable=unused-argument
-""" Module for the base OCB Block. """
+""" Module for the base Block."""
from typing import TYPE_CHECKING, Optional, OrderedDict, Tuple, Union
@@ -17,16 +17,16 @@
)
from pyflow.core.serializable import Serializable
-from pyflow.core.socket import OCBSocket
-from pyflow.blocks.widgets import OCBSplitter, OCBSizeGrip, OCBTitle
+from pyflow.core.socket import Socket
+from pyflow.blocks.widgets import Splitter, SizeGrip, Title
if TYPE_CHECKING:
- from pyflow.scene.scene import OCBScene
+ from pyflow.scene.scene import Scene
BACKGROUND_COLOR = QColor("#E3212121")
-class OCBBlock(QGraphicsItem, Serializable):
+class Block(QGraphicsItem, Serializable):
"""Base class for blocks in Pyflow."""
@@ -49,7 +49,7 @@ def __init__(
width: int = DEFAULT_DATA["width"],
height: int = DEFAULT_DATA["height"],
edge_size: float = 10.0,
- title: Union[OCBTitle, str] = DEFAULT_DATA["title"],
+ title: Union[Title, str] = DEFAULT_DATA["title"],
parent: Optional["QGraphicsItem"] = None,
):
"""Base class for blocks in Pyflow.
@@ -86,14 +86,14 @@ def __init__(
self.root.setAttribute(Qt.WA_TranslucentBackground)
self.root.setGeometry(0, 0, int(width), int(height))
- self.title_widget = OCBTitle(title, parent=self.root)
+ self.title_widget = Title(title, parent=self.root)
self.title_widget.setAttribute(Qt.WA_TranslucentBackground)
- self.splitter = OCBSplitter(self, Qt.Vertical, self.root)
+ self.splitter = Splitter(self, Qt.Vertical, self.root)
- self.size_grip = OCBSizeGrip(self, self.root)
+ self.size_grip = SizeGrip(self, self.root)
- if type(self) == OCBBlock:
+ if type(self) == Block:
# This has to be called at the end of the constructor of
# every class inheriting this.
self.holder.setWidget(self.root)
@@ -107,8 +107,8 @@ def __init__(
self.moved = False
self.metadata = {}
- def scene(self) -> "OCBScene":
- """Get the current OCBScene containing the block."""
+ def scene(self) -> "Scene":
+ """Get the current Scene containing the block."""
return super().scene()
def boundingRect(self) -> QRectF:
@@ -139,12 +139,12 @@ def paint(
0, 0, self.width, self.height, self.edge_size, self.edge_size
)
painter.setPen(
- self._pen_outline_selected if self.isSelected() else self._pen_outline
+ self._pen_outline_selected if self.isSelected() else self.pen_outline
)
painter.setBrush(Qt.BrushStyle.NoBrush)
painter.drawPath(path_outline.simplified())
- def get_socket_pos(self, socket: OCBSocket) -> Tuple[float]:
+ def get_socket_pos(self, socket: Socket) -> Tuple[float]:
"""Get a socket position to place them on the block sides."""
if socket.socket_type == "input":
x = 0
@@ -166,7 +166,7 @@ def update_sockets(self):
for socket in self.sockets_in + self.sockets_out:
socket.setPos(*self.get_socket_pos(socket))
- def add_socket(self, socket: OCBSocket):
+ def add_socket(self, socket: Socket):
"""Add a socket to the block."""
if socket.socket_type == "input":
self.sockets_in.append(socket)
@@ -174,7 +174,7 @@ def add_socket(self, socket: OCBSocket):
self.sockets_out.append(socket)
self.update_sockets()
- def remove_socket(self, socket: OCBSocket):
+ def remove_socket(self, socket: Socket):
"""Remove a socket from the block."""
if socket.socket_type == "input":
self.sockets_in.remove(socket)
@@ -184,14 +184,14 @@ def remove_socket(self, socket: OCBSocket):
self.update_sockets()
def mouseReleaseEvent(self, event: QGraphicsSceneMouseEvent):
- """OCBBlock reaction to a mouseReleaseEvent."""
+ """Block reaction to a mouseReleaseEvent."""
if self.moved:
self.moved = False
self.scene().history.checkpoint("Moved block", set_modified=True)
super().mouseReleaseEvent(event)
def mouseMoveEvent(self, event: QGraphicsSceneMouseEvent):
- """OCBBlock reaction to a mouseMoveEvent."""
+ """Block reaction to a mouseMoveEvent."""
super().mouseMoveEvent(event)
self.moved = True
@@ -204,7 +204,7 @@ def remove(self):
scene.removeItem(self)
def update_splitter(self):
- """Change the geometry of the splitter to match the block"""
+ """Change the geometry of the splitter to match the block."""
# We make the resizing of splitter only affect
# the last element of the split view
self.splitter.setGeometry(
@@ -215,7 +215,7 @@ def update_splitter(self):
)
def update_title(self):
- """Change the geometry of the title to match the block"""
+ """Change the geometry of the title to match the block."""
self.title_widget.setGeometry(
int(self.edge_size),
int(self.edge_size / 2),
@@ -224,7 +224,7 @@ def update_title(self):
)
def update_size_grip(self):
- """Change the geometry of the size grip to match the block"""
+ """Change the geometry of the size grip to match the block."""
self.size_grip.setGeometry(
int(self.width - self.edge_size * 2),
int(self.height - self.edge_size * 2),
@@ -267,8 +267,13 @@ def height(self):
def height(self, value: float):
self.root.setGeometry(0, 0, self.root.width(), int(value))
+ @property
+ def pen_outline(self) -> QPen:
+ """The current pen used to draw the outline of the Block."""
+ return self._pen_outline
+
def serialize(self) -> OrderedDict:
- """Return a serialized version of this widget"""
+ """Return a serialized version of this widget."""
self.metadata.update({"title_metadata": self.title_widget.serialize()})
metadata = OrderedDict(sorted(self.metadata.items()))
return OrderedDict(
@@ -292,7 +297,7 @@ def serialize(self) -> OrderedDict:
)
def deserialize(self, data: dict, hashmap: dict = None, restore_id=True) -> None:
- """Restore the block from serialized data"""
+ """Restore the block from serialized data."""
if restore_id and "id" in data:
self.id = data["id"]
@@ -317,7 +322,7 @@ def deserialize(self, data: dict, hashmap: dict = None, restore_id=True) -> None
# Deserialize new sockets
for socket_data in data["sockets"]:
- socket = OCBSocket(block=self)
+ socket = Socket(block=self)
socket.deserialize(socket_data, hashmap, restore_id)
self.add_socket(socket)
if hashmap is not None:
diff --git a/blocks/empty.pfb b/pyflow/blocks/blockfiles/empty.pfb
similarity index 90%
rename from blocks/empty.pfb
rename to pyflow/blocks/blockfiles/empty.pfb
index 8469d16b..a068cf06 100644
--- a/blocks/empty.pfb
+++ b/pyflow/blocks/blockfiles/empty.pfb
@@ -1,6 +1,6 @@
{
"title": "Code",
- "block_type": "OCBCodeBlock",
+ "block_type": "CodeBlock",
"source": "",
"stdout": "",
"image": "",
diff --git a/blocks/markdown.pfb b/pyflow/blocks/blockfiles/markdown.pfb
similarity index 88%
rename from blocks/markdown.pfb
rename to pyflow/blocks/blockfiles/markdown.pfb
index b47dc633..ab379a48 100644
--- a/blocks/markdown.pfb
+++ b/pyflow/blocks/blockfiles/markdown.pfb
@@ -1,6 +1,6 @@
{
"title": "Markdown",
- "block_type": "OCBMarkdownBlock",
+ "block_type": "MarkdownBlock",
"text": "",
"splitter_pos": [88,41],
"width": 618,
diff --git a/pyflow/blocks/codeblock.py b/pyflow/blocks/codeblock.py
index 96b54de3..2329b3e6 100644
--- a/pyflow/blocks/codeblock.py
+++ b/pyflow/blocks/codeblock.py
@@ -1,29 +1,23 @@
# Pyflow an open-source tool for modular visual programing in python
-# Copyright (C) 2021 Mathïs FEDERICO
+# Copyright (C) 2021-2022 Bycelium
-""" Module for the base OCB Code Block. """
+""" Module for the base Code Block."""
-from typing import OrderedDict, Optional
-from PyQt5.QtWidgets import (
- QPushButton,
- QTextEdit,
- QWidget,
- QStyleOptionGraphicsItem,
-)
-from PyQt5.QtCore import Qt
-from PyQt5.QtGui import QPen, QColor, QPainter, QPainterPath
+from typing import OrderedDict
+from PyQt5.QtWidgets import QPushButton, QTextEdit
+from PyQt5.QtGui import QPen, QColor
from ansi2html import Ansi2HTMLConverter
-from pyflow.blocks.block import OCBBlock
+from pyflow.blocks.block import Block
-from pyflow.blocks.executableblock import OCBExecutableBlock
+from pyflow.blocks.executableblock import ExecutableBlock
from pyflow.core.pyeditor import PythonEditor
conv = Ansi2HTMLConverter()
-class OCBCodeBlock(OCBExecutableBlock):
+class CodeBlock(ExecutableBlock):
"""
Code Block
@@ -36,16 +30,17 @@ class OCBCodeBlock(OCBExecutableBlock):
"""
DEFAULT_DATA = {
- **OCBBlock.DEFAULT_DATA,
+ **Block.DEFAULT_DATA,
"source": "",
}
- MANDATORY_FIELDS = OCBBlock.MANDATORY_FIELDS
+ MANDATORY_FIELDS = Block.MANDATORY_FIELDS
def __init__(self, source: str = "", **kwargs):
"""
- Create a new OCBCodeBlock.
- Initialize all the child widgets specific to this block type
+ Create a new CodeBlock.
+ Initialize all the child widgets specific to this block type.
+
"""
super().__init__(**kwargs)
@@ -66,13 +61,10 @@ def __init__(self, source: str = "", **kwargs):
self.has_been_run = False
self.blocks_to_run = []
- 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,
+ QPen(QColor("#7F000000")), # Idle
+ QPen(QColor("#FF0000")), # Running
+ QPen(QColor("#00ff00")), # Transmitting
]
# Add output pannel
@@ -92,14 +84,14 @@ def __init__(self, source: str = "", **kwargs):
self.update_all() # Set the geometry of display and source_editor
def init_output_panel(self):
- """Initialize the output display widget: QLabel"""
+ """Initialize the output display widget: QLabel."""
output_panel = QTextEdit()
output_panel.setReadOnly(True)
output_panel.setFont(self.source_editor.font())
return output_panel
def init_run_button(self):
- """Initialize the run button"""
+ """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))
@@ -107,7 +99,7 @@ def init_run_button(self):
return run_button
def init_run_all_button(self):
- """Initialize the run all button"""
+ """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.handle_run_right)
@@ -116,21 +108,21 @@ def init_run_all_button(self):
return run_all_button
def handle_run_right(self):
- """Called when the button for "Run All" was pressed"""
- if self.run_color != 0:
+ """Called when the button for "Run All" was pressed."""
+ if self.run_state != 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:
+ """Called when the button for "Run Left" was pressed."""
+ if self.run_state != 0:
self._interrupt_execution()
else:
self.run_left()
def run_code(self):
- """Run the code in the block"""
+ """Run the code in the block."""
# Reset stdout
self._cached_stdout = ""
@@ -141,8 +133,13 @@ def run_code(self):
super().run_code() # actually run the code
+ def execution_finished(self):
+ super().execution_finished()
+ self.run_button.setText(">")
+ self.run_all_button.setText(">>")
+
def update_title(self):
- """Change the geometry of the title widget"""
+ """Change the geometry of the title widget."""
self.title_widget.setGeometry(
int(self.edge_size) + self.run_button.width(),
int(self.edge_size / 2),
@@ -151,7 +148,7 @@ def update_title(self):
)
def update_output_panel(self):
- """Change the geometry of the output panel"""
+ """Change the geometry of the output panel."""
# Close output panel if no output
if self.stdout == "":
self.previous_splitter_size = self.splitter.sizes()
@@ -159,50 +156,21 @@ def update_output_panel(self):
self.splitter.setSizes([1, 0])
def update_run_all_button(self):
- """Change the geometry of the run all button"""
+ """Change the geometry of the run all button."""
self.run_all_button.move(
int(self.width - self.edge_size - self.run_button.width()),
int(self.edge_size / 2),
)
def update_all(self):
- """Update the code block parts"""
+ """Update the code block parts."""
super().update_all()
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"""
+ """Source code."""
return self._source
@source.setter
@@ -217,19 +185,13 @@ def source(self, value: str):
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()
+ def pen_outline(self) -> QPen:
+ """The current pen used to draw the outline of the CodeBlock."""
+ return self._pen_outlines[self.run_state]
@property
def stdout(self) -> str:
- """Access the content of the output panel of the block"""
+ """Access the content of the output panel of the block."""
return self._stdout
@stdout.setter
@@ -254,8 +216,8 @@ def stdout(self, value: str):
self.splitter.setSizes([1, 0])
@staticmethod
- def str_to_html(text: str):
- """Format text so that it's properly displayed by the code block"""
+ def str_to_html(text: str) -> str:
+ """Format text so that it's properly displayed by the code block."""
# Remove carriage returns and backspaces
text = text.replace("\x08", "")
text = text.replace("\r", "")
@@ -268,7 +230,7 @@ def str_to_html(text: str):
return text
def handle_stdout(self, value: str):
- """Handle the stdout signal"""
+ """Handle the stdout signal."""
# If there is a new line
# Save every line but the last one
@@ -281,16 +243,16 @@ def handle_stdout(self, value: str):
self.stdout = self._cached_stdout + value
@staticmethod
- def b64_to_html(image: str):
- """Transform a base64 encoded image into a html image"""
+ def b64_to_html(image: str) -> str:
+ """Transform a base64 encoded image into a html image."""
return f''
def handle_image(self, image: str):
- """Handle the image signal"""
+ """Handle the image signal."""
self.stdout = "" + image
def serialize(self):
- """Serialize the code block"""
+ """Serialize the code block."""
base_dict = super().serialize()
base_dict["source"] = self.source
base_dict["stdout"] = self.stdout
@@ -300,7 +262,7 @@ def serialize(self):
def deserialize(
self, data: OrderedDict, hashmap: dict = None, restore_id: bool = True
):
- """Restore a codeblock from it's serialized state"""
+ """Restore a codeblock from it's serialized state."""
self.complete_with_default(data)
diff --git a/pyflow/blocks/containerblock.py b/pyflow/blocks/containerblock.py
index b2aec37b..af385ba6 100644
--- a/pyflow/blocks/containerblock.py
+++ b/pyflow/blocks/containerblock.py
@@ -1,12 +1,17 @@
-"""
-Exports OCBContainerBlock.
+# Pyflow an open-source tool for modular visual programing in python
+# Copyright (C) 2021-2022 Bycelium
+
+""" Module for the ContainerBlock.
+
+A block that can contain other blocks.
+
"""
from PyQt5.QtWidgets import QVBoxLayout
-from pyflow.blocks.block import OCBBlock
+from pyflow.blocks.block import Block
-class OCBContainerBlock(OCBBlock):
+class ContainerBlock(Block):
"""
A block that can contain other blocks.
"""
@@ -18,10 +23,9 @@ def __init__(self, **kwargs):
# Due to the overall structure of the code, this cannot be removed, as the
# scene should be able to serialize blocks.
# This is not due to bad code design and should not be removed.
- from pyflow.graphics.view import (
- OCBView,
- ) # pylint: disable=cyclic-import
- from pyflow.scene.scene import OCBScene # pylint: disable=cyclic-import
+ # pylint: disable=import-outside-toplevel, cyclic-import
+ from pyflow.graphics.view import View
+ from pyflow.scene.scene import Scene
self.layout = QVBoxLayout(self.root)
self.layout.setContentsMargins(
@@ -31,8 +35,8 @@ def __init__(self, **kwargs):
self.edge_size * 2,
)
- self.child_scene = OCBScene()
- self.child_view = OCBView(self.child_scene)
+ self.child_scene = Scene()
+ self.child_view = View(self.child_scene)
self.layout.addWidget(self.child_view)
self.holder.setWidget(self.root)
diff --git a/pyflow/blocks/drawingblock.py b/pyflow/blocks/drawingblock.py
index 7a7b3a64..4ae08cfe 100644
--- a/pyflow/blocks/drawingblock.py
+++ b/pyflow/blocks/drawingblock.py
@@ -1,6 +1,12 @@
+# Pyflow an open-source tool for modular visual programing in python
+# Copyright (C) 2021-2022 Bycelium
# pylint:disable=unused-argument
-""" Module for the base OCB Drawing Block. """
+""" Module for the Drawing Block.
+
+A block in which you can draw.
+
+"""
from math import floor
import json
@@ -9,19 +15,19 @@
from PyQt5.QtCore import Qt, pyqtSignal
from PyQt5.QtGui import QColor, QMouseEvent, QPaintEvent, QPainter
from PyQt5.QtWidgets import QPushButton, QWidget
-from pyflow.blocks.executableblock import OCBExecutableBlock
+from pyflow.blocks.executableblock import ExecutableBlock
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
@@ -35,13 +41,13 @@ def __init__(self, parent: QWidget):
self.color_buffer[-1].append(0)
def clearDrawing(self):
- """Clear the drawing"""
+ """Clear the drawing."""
for i in range(self.pixel_width):
for j in range(self.pixel_height):
self.color_buffer[i][j] = 0
def paintEvent(self, evt: QPaintEvent):
- """Draw the content of the widget"""
+ """Draw the content of the widget."""
painter = QPainter(self)
for i in range(self.pixel_width):
@@ -61,7 +67,7 @@ def paintEvent(self, evt: QPaintEvent):
)
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)
@@ -71,20 +77,20 @@ def mouseMoveEvent(self, evt: QMouseEvent):
self.on_value_changed.emit()
def mousePressEvent(self, evt: QMouseEvent):
- """Signal that the drawing starts"""
+ """Signal that the drawing starts."""
self.mouse_down = True
def mouseReleaseEvent(self, evt: QMouseEvent):
- """Signal that the drawing stops"""
+ """Signal that the drawing stops."""
self.mouse_down = False
-class OCBDrawingBlock(OCBExecutableBlock):
+class DrawingBlock(ExecutableBlock):
- """An OCBBlock on which you can draw, to test your CNNs for example"""
+ """An Block on which you can draw, to test your CNNs for example."""
def __init__(self, **kwargs):
- """Create a new OCBBlock"""
+ """Create a new Block."""
super().__init__(**kwargs)
self.draw_area = DrawableWidget(self.root)
@@ -103,7 +109,7 @@ def __init__(self, **kwargs):
@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
@@ -111,7 +117,7 @@ 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
@@ -125,7 +131,7 @@ def valueChanged(self):
@property
def source(self):
- """The "source code" of the drawingblock i.e an assignement to the drawing buffer"""
+ """The "source code" of the drawingblock i.e an assignement to the drawing buffer."""
python_code = f"{self.var_name} = {repr(self.draw_area.color_buffer)}"
return python_code
@@ -136,7 +142,7 @@ def source(self, value: str):
def deserialize(
self, data: OrderedDict, hashmap: dict = None, restore_id: bool = True
):
- """Restore a markdown block from it's serialized state"""
+ """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/pyflow/blocks/executableblock.py b/pyflow/blocks/executableblock.py
index 590a0e0b..8867098f 100644
--- a/pyflow/blocks/executableblock.py
+++ b/pyflow/blocks/executableblock.py
@@ -1,15 +1,22 @@
-""" Module for the executable block class """
+# Pyflow an open-source tool for modular visual programing in python
+# Copyright (C) 2021-2022 Bycelium
+
+""" Module for the abstract ExecutableBlock class.
+
+An abstract block that allows for execution, like CodeBlocks and Sliders.
+
+"""
from typing import OrderedDict
from abc import abstractmethod
from PyQt5.QtCore import QTimer
from PyQt5.QtWidgets import QApplication
-from pyflow.blocks.block import OCBBlock
-from pyflow.core.socket import OCBSocket
+from pyflow.blocks.block import Block
+from pyflow.core.socket import Socket
-class OCBExecutableBlock(OCBBlock):
+class ExecutableBlock(Block):
"""
Executable Block
@@ -17,8 +24,8 @@ class OCBExecutableBlock(OCBBlock):
This block type is not meant to be instanciated !
It's an abstract class that represents blocks that can be executed like:
- - OCBCodeBlock
- - OCBSlider
+ - CodeBlock
+ - Slider
"""
@@ -30,9 +37,7 @@ def __init__(self, **kwargs):
super().__init__(**kwargs)
self.has_been_run = False
-
- # 0 for normal, 1 for running, 2 for transmitting
- self.run_color = 0
+ self._run_state = 0
# Each element is a list of blocks/edges to be animated
# Running will paint each element one after the other
@@ -42,31 +47,31 @@ def __init__(self, **kwargs):
# Add execution flow sockets
exe_sockets = (
- OCBSocket(self, socket_type="input", flow_type="exe"),
- OCBSocket(self, socket_type="output", flow_type="exe"),
+ Socket(self, socket_type="input", flow_type="exe"),
+ Socket(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")
+ if type(self) == ExecutableBlock:
+ raise RuntimeError("ExecutableBlock should not be instanciated directly")
def has_input(self) -> bool:
- """Checks whether a block has connected input blocks"""
+ """Checks whether a block has connected input blocks."""
for input_socket in self.sockets_in:
- if len(input_socket.edges) != 0:
+ if input_socket.edges:
return True
return False
def has_output(self) -> bool:
- """Checks whether a block has connected output blocks"""
+ """Checks whether a block has connected output blocks."""
for output_socket in self.sockets_out:
- if len(output_socket.edges) != 0:
+ if output_socket.edges:
return True
return False
def run_code(self):
- """Run the code in the block"""
+ """Run the code in the block."""
# Queue the code to execute
code = self.source
@@ -78,14 +83,12 @@ def run_code(self):
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(">>")
+ """Reset the text of the run buttons."""
+ self.run_state = 0
self.blocks_to_run = []
def _interrupt_execution(self):
- """Interrupt an execution, reset the blocks in the queue"""
+ """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()
@@ -104,7 +107,7 @@ def transmitting_animation_in(self):
"""
for elem in self.transmitting_queue[0]:
# Set color to transmitting
- elem.run_color = 2
+ elem.run_state = 2
QApplication.processEvents()
QTimer.singleShot(self.transmitting_delay, self.transmitting_animation_out)
@@ -117,13 +120,13 @@ def transmitting_animation_out(self):
# 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
+ elem.run_state = 0
else:
- elem.run_color = 0
+ elem.run_state = 0
QApplication.processEvents()
self.transmitting_queue.pop(0)
- if len(self.transmitting_queue) != 0:
+ if self.transmitting_queue:
# If the queue is not empty, move forward in the animation
self.transmitting_animation_in()
else:
@@ -148,7 +151,7 @@ def custom_bfs(self, start_node, reverse=False):
to_transmit = [[start_node]]
to_visit = [start_node]
- while len(to_visit) != 0:
+ while to_visit:
# Remove duplicates
to_visit = list(set(to_visit))
@@ -203,7 +206,7 @@ def right_traversal(self):
next_edges = []
next_blocks = []
- while len(to_visit_input) != 0 or len(to_visit_output) != 0:
+ while to_visit_input or to_visit_output:
for block in to_visit_input.copy():
# Check input edges and blocks
for input_socket in block.sockets_in:
@@ -243,7 +246,7 @@ def right_traversal(self):
return to_transmit
def run_blocks(self):
- """Run a list of blocks"""
+ """Run a list of blocks."""
for block in self.blocks_to_run[::-1]:
if not block.has_been_run:
block.run_code()
@@ -251,13 +254,13 @@ def run_blocks(self):
self.run_code()
def run_left(self):
- """Run all of the block's dependencies and then run the block"""
+ """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:
+ if self.transmitting_queue:
return
# Gather dependencies
@@ -274,10 +277,10 @@ def run_left(self):
self.transmitting_animation_in()
def run_right(self):
- """Run all of the output blocks and all their dependencies"""
+ """Run all of the output blocks and all their dependencies."""
# To avoid crashing when spamming the button
- if len(self.transmitting_queue) != 0:
+ if self.transmitting_queue:
return
# Create transmitting queue
@@ -301,13 +304,13 @@ def run_right(self):
self.transmitting_animation_in()
def reset_has_been_run(self):
- """Called when the output is an error"""
+ """Called when the output is an error."""
self.has_been_run = False
@property
@abstractmethod
def source(self) -> str:
- """Source code"""
+ """Source code."""
raise NotImplementedError("source(self) should be overriden")
@source.setter
@@ -315,18 +318,36 @@ def source(self) -> str:
def source(self, value: str):
raise NotImplementedError("source(self) should be overriden")
+ @property
+ def run_state(self) -> int:
+ """Run state.
+
+ Describe the current state of the ExecutableBlock:
+ - 0: idle.
+ - 1: running.
+ - 2: transmitting.
+
+ """
+ return self._run_state
+
+ @run_state.setter
+ def run_state(self, value: int):
+ self._run_state = value
+ # Update to force repaint
+ self.update()
+
def handle_stdout(self, value: str):
- """Handle the stdout signal"""
+ """Handle the stdout signal."""
def handle_image(self, image: str):
- """Handle the image signal"""
+ """Handle the image signal."""
def serialize(self):
- """Return a serialized version of this block"""
+ """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"""
+ """Restore a codeblock from it's serialized state."""
super().deserialize(data, hashmap, restore_id)
diff --git a/pyflow/blocks/markdownblock.py b/pyflow/blocks/markdownblock.py
index d184030c..1b1474bd 100644
--- a/pyflow/blocks/markdownblock.py
+++ b/pyflow/blocks/markdownblock.py
@@ -1,5 +1,10 @@
-"""
-Exports OCBMarkdownBlock.
+# Pyflow an open-source tool for modular visual programing in python
+# Copyright (C) 2021-2022 Bycelium
+
+""" Module for the MarkdownBlock.
+
+A block able to render Markdown.
+
"""
from typing import OrderedDict
@@ -9,16 +14,16 @@
from PyQt5.Qsci import QsciLexerMarkdown, QsciScintilla
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QColor, QFont
-from pyflow.blocks.block import OCBBlock
+from pyflow.blocks.block import Block
from pyflow.graphics.theme_manager import theme_manager
-class OCBMarkdownBlock(OCBBlock):
- """A block that is able to render markdown text"""
+class MarkdownBlock(Block):
+ """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 MarkdownBlock, a block that renders markdown
"""
super().__init__(**kwargs)
@@ -53,7 +58,7 @@ def __init__(self, **kwargs):
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 = """
@@ -69,7 +74,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
@@ -86,7 +91,7 @@ def serialize(self):
def deserialize(
self, data: OrderedDict, hashmap: dict = None, restore_id: bool = True
):
- """Restore a markdown block from it's serialized state"""
+ """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/pyflow/blocks/sliderblock.py b/pyflow/blocks/sliderblock.py
index e64615fe..fb101a97 100644
--- a/pyflow/blocks/sliderblock.py
+++ b/pyflow/blocks/sliderblock.py
@@ -1,16 +1,19 @@
# Pyflow an open-source tool for modular visual programing in python
+# Copyright (C) 2021-2022 Bycelium
+
+""" Module for the SliderBlock.
+
+A block that can allows dynamic value modification using a slider.
-"""
-Exports OCBSliderBlock.
"""
from typing import OrderedDict
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QHBoxLayout, QLabel, QLineEdit, QSlider, QVBoxLayout
-from pyflow.blocks.executableblock import OCBExecutableBlock
+from pyflow.blocks.executableblock import ExecutableBlock
-class OCBSliderBlock(OCBExecutableBlock):
+class SliderBlock(ExecutableBlock):
"""
Features a slider ranging from 0 to 1 and an area to choose what value to assign the slider to.
"""
@@ -45,7 +48,7 @@ def __init__(self, **kwargs):
self.holder.setWidget(self.root)
def valueChanged(self):
- """This is called when the value of the slider changes"""
+ """This is called when the value of the slider changes."""
self.variable_value.setText(f"{self.value}")
# Make sure that the slider is initialized before trying to run it.
if self.scene() is not None:
@@ -53,7 +56,7 @@ def valueChanged(self):
@property
def source(self):
- """The "source code" of the slider i.e an assignement to the value of the slider"""
+ """The "source code" of the slider i.e an assignement to the value of the slider."""
python_code = f"{self.var_name} = {self.value}"
return python_code
@@ -63,7 +66,7 @@ def source(self, value: str):
@property
def value(self):
- """The value of the slider"""
+ """The value of the slider."""
return str(self.slider.value() / 100)
@value.setter
@@ -72,7 +75,7 @@ def value(self, value: str):
@property
def var_name(self):
- """The name of the python variable associated with the slider"""
+ """The name of the python variable associated with the slider."""
return self.variable_text.text()
@var_name.setter
@@ -80,7 +83,7 @@ def var_name(self, value: str):
self.variable_text.setText(value)
def serialize(self):
- """Return a serialized version of this widget"""
+ """Return a serialized version of this widget."""
base_dict = super().serialize()
base_dict["value"] = self.value
base_dict["var_name"] = self.var_name
@@ -90,7 +93,7 @@ def serialize(self):
def deserialize(
self, data: OrderedDict, hashmap: dict = None, restore_id: bool = True
):
- """Restore a slider block from it's serialized state"""
+ """Restore a slider block from it's serialized state."""
for dataname in ["value", "var_name"]:
if dataname in data:
setattr(self, dataname, data[dataname])
diff --git a/pyflow/blocks/widgets/__init__.py b/pyflow/blocks/widgets/__init__.py
index bcbaef76..4d6a5b5a 100644
--- a/pyflow/blocks/widgets/__init__.py
+++ b/pyflow/blocks/widgets/__init__.py
@@ -1,8 +1,8 @@
# Pyflow an open-source tool for modular visual programing in python
-# Copyright (C) 2021 Mathïs FEDERICO
+# Copyright (C) 2021-2022 Bycelium
-""" Module for the OCB Blocks Widgets. """
+""" Module for the Blocks Widgets. """
-from pyflow.blocks.widgets.blocksplitter import OCBSplitter
-from pyflow.blocks.widgets.blocktitle import OCBTitle
-from pyflow.blocks.widgets.blocksizegrip import OCBSizeGrip
+from pyflow.blocks.widgets.blocksplitter import Splitter
+from pyflow.blocks.widgets.blocktitle import Title
+from pyflow.blocks.widgets.blocksizegrip import SizeGrip
diff --git a/pyflow/blocks/widgets/blocksizegrip.py b/pyflow/blocks/widgets/blocksizegrip.py
index 6b65fa53..439f9c75 100644
--- a/pyflow/blocks/widgets/blocksizegrip.py
+++ b/pyflow/blocks/widgets/blocksizegrip.py
@@ -1,5 +1,7 @@
-"""
-Implements the SizeGrip Widget for the Blocks.
+# Pyflow an open-source tool for modular visual programing in python
+# Copyright (C) 2021-2022 Bycelium
+
+""" Module for SizeGrip block widget.
The size grip is the little icon at the bottom right of a block that is used to
resize a block.
@@ -10,15 +12,15 @@
from PyQt5.QtGui import QMouseEvent
-class OCBSizeGrip(QSizeGrip):
- """A grip to resize a block"""
+class SizeGrip(QSizeGrip):
+ """A grip to resize a block."""
def __init__(self, block: QGraphicsItem, parent: QWidget = None):
"""
Constructor for BlockSizeGrip
block is the QGraphicsItem holding the QSizeGrip.
- It's usually an OCBBlock
+ It's usually an Block
"""
super().__init__(parent)
self.mouseX = 0
@@ -27,7 +29,7 @@ def __init__(self, block: QGraphicsItem, parent: QWidget = None):
self.resizing = False
def mousePressEvent(self, mouseEvent: QMouseEvent):
- """Start the resizing"""
+ """Start the resizing."""
self.mouseX = mouseEvent.globalX()
self.mouseY = mouseEvent.globalY()
self.resizing = True
@@ -35,17 +37,17 @@ def mousePressEvent(self, mouseEvent: QMouseEvent):
def mouseReleaseEvent(
self, mouseEvent: QMouseEvent
): # pylint:disable=unused-argument
- """Stop the resizing"""
+ """Stop the resizing."""
self.resizing = False
self.block.scene().history.checkpoint("Resized block", set_modified=True)
@property
def _zoom(self) -> float:
- """Returns how much the scene is"""
+ """Returns how much the scene is."""
return self.block.scene().views()[0].zoom
def mouseMoveEvent(self, mouseEvent: QMouseEvent):
- """Performs resizing of the root widget"""
+ """Performs resizing of the root widget."""
transformed_pt1 = self.block.mapFromScene(QPoint(0, 0))
transformed_pt2 = self.block.mapFromScene(QPoint(1, 1))
diff --git a/pyflow/blocks/widgets/blocksplitter.py b/pyflow/blocks/widgets/blocksplitter.py
index a7eee746..d04db5e8 100644
--- a/pyflow/blocks/widgets/blocksplitter.py
+++ b/pyflow/blocks/widgets/blocksplitter.py
@@ -1,31 +1,35 @@
-"""
-Module defining a Splitter, the widget that contains multiple areas inside
-a block and allows the user to resize those areas.
+# Pyflow an open-source tool for modular visual programing in python
+# Copyright (C) 2021-2022 Bycelium
+
+""" Module for the Splitter block widget.
+
+The Splitter contains multiple areas inside a block
+and allows the user to resize those areas.
"""
from PyQt5.QtGui import QMouseEvent
from PyQt5.QtWidgets import QSplitter, QSplitterHandle, QWidget
-class OCBSplitterHandle(QSplitterHandle):
- """A handle for splitters with undoable events"""
+class SplitterHandle(QSplitterHandle):
+ """A handle for splitters with undoable events."""
def mouseReleaseEvent(self, evt: QMouseEvent):
- """When releasing the handle, save the state to history"""
+ """When releasing the handle, save the state to history."""
scene = self.parent().block.scene()
if scene is not None:
scene.history.checkpoint("Resize block", set_modified=True)
return super().mouseReleaseEvent(evt)
-class OCBSplitter(QSplitter):
- """A spliter with undoable events"""
+class Splitter(QSplitter):
+ """A spliter with undoable events."""
def __init__(self, block: QWidget, orientation: int, parent: QWidget):
- """Create a new OCBSplitter"""
+ """Create a new Splitter."""
super().__init__(orientation, parent)
self.block = block
def createHandle(self):
- """Return the middle handle of the splitter"""
- return OCBSplitterHandle(self.orientation(), self)
+ """Return the middle handle of the splitter."""
+ return SplitterHandle(self.orientation(), self)
diff --git a/pyflow/blocks/widgets/blocktitle.py b/pyflow/blocks/widgets/blocktitle.py
index de187992..e5538c3a 100644
--- a/pyflow/blocks/widgets/blocktitle.py
+++ b/pyflow/blocks/widgets/blocktitle.py
@@ -1,7 +1,10 @@
+# Pyflow an open-source tool for modular visual programing in python
+# Copyright (C) 2021-2022 Bycelium
# pylint:disable=unused-argument
-"""
-Module defining the widget for the title of blocks.
-It's a QLineEdit modified so that editing it requires a double click.
+
+""" Module for the Title block widget.
+
+The Title is a modified QLineEdit for PyFlow purpose.
"""
import time
@@ -13,8 +16,8 @@
from pyflow.core.serializable import Serializable
-class OCBTitle(QLineEdit, Serializable):
- """The title of an OCBBlock. Needs to be double clicked to interact"""
+class Title(QLineEdit, Serializable):
+ """The title of an Block. Needs to be double clicked to interact."""
def __init__(
self,
@@ -24,7 +27,7 @@ def __init__(
size: int = 12,
parent: QWidget = None,
):
- """Create a new title for an OCBBlock"""
+ """Create a new title for an Block."""
Serializable.__init__(self)
QLineEdit.__init__(self, text, parent)
self.clickTime = None
@@ -33,7 +36,7 @@ def __init__(
self.setCursorPosition(0)
def init_ui(self, color: str, font: str, size: int):
- """Apply the style given to the title"""
+ """Apply the style given to the title."""
self.color = color
self.setStyleSheet(
f"""
@@ -62,19 +65,19 @@ def mousePressEvent(self, event: QMouseEvent):
self.clickTime = time.time()
def focusOutEvent(self, event: QFocusEvent):
- """The title is read-only when focused is lost"""
+ """The title is read-only when focused is lost."""
self.setReadOnly(True)
self.setCursorPosition(0)
self.deselect()
def mouseDoubleClickEvent(self, event: QMouseEvent):
- """Toggle readonly mode when double clicking"""
+ """Toggle readonly mode when double clicking."""
self.setReadOnly(not self.isReadOnly())
if not self.isReadOnly():
self.setFocus(Qt.MouseFocusReason)
def serialize(self) -> OrderedDict:
- """Return a serialized version of this widget"""
+ """Return a serialized version of this widget."""
return OrderedDict(
[
("color", self.color),
@@ -84,7 +87,7 @@ def serialize(self) -> OrderedDict:
)
def deserialize(self, data: OrderedDict, hashmap: dict = None, restore_id=True):
- """Restore a title from serialized data"""
+ """Restore a title from serialized data."""
if restore_id:
self.id = data.get("id", id(self))
self.init_ui(data["color"], data["font"], data["size"])
diff --git a/pyflow/core/__init__.py b/pyflow/core/__init__.py
index a474fac2..cbf0a267 100644
--- a/pyflow/core/__init__.py
+++ b/pyflow/core/__init__.py
@@ -1,2 +1,2 @@
# Pyflow an open-source tool for modular visual programing in python
-# Copyright (C) 2021 Mathïs FEDERICO
+# Copyright (C) 2021-2022 Bycelium
diff --git a/pyflow/core/edge.py b/pyflow/core/edge.py
index 7f1b297e..d3fc31dd 100644
--- a/pyflow/core/edge.py
+++ b/pyflow/core/edge.py
@@ -1,7 +1,7 @@
# Pyflow an open-source tool for modular visual programing in python
-# Copyright (C) 2021 Mathïs FEDERICO
+# Copyright (C) 2021-2022 Bycelium
-""" Module for the OCB Edge. """
+""" Module for the base Edge."""
from __future__ import annotations
@@ -16,10 +16,10 @@
)
from pyflow.core.serializable import Serializable
-from pyflow.core.socket import OCBSocket
+from pyflow.core.socket import Socket
-class OCBEdge(QGraphicsPathItem, Serializable):
+class Edge(QGraphicsPathItem, Serializable):
"""Base class for directed edges in Pyflow."""
@@ -36,8 +36,8 @@ def __init__(
edge_transmitting_color="#00ff00",
source: QPointF = QPointF(0, 0),
destination: QPointF = QPointF(0, 0),
- source_socket: OCBSocket = None,
- destination_socket: OCBSocket = None,
+ source_socket: Socket = None,
+ destination_socket: Socket = None,
):
"""Base class for edges in Pyflow.
@@ -74,7 +74,7 @@ def __init__(
self.pens = [self._pen, self._pen_running, self._pen_transmitting]
# 0 for normal, 1 for running, 2 for transmitting
- self.run_color = 0
+ self.run_state = 0
self.setFlag(QGraphicsPathItem.GraphicsItemFlag.ItemIsSelectable)
self.setZValue(-1)
@@ -96,7 +96,7 @@ def remove_from_socket(self, socket_type="source"):
"""
socket_name = f"{socket_type}_socket"
- socket = getattr(self, socket_name, OCBSocket)
+ socket = getattr(self, socket_name, Socket)
if socket is not None:
socket.remove_edge(self)
setattr(self, socket_name, None)
@@ -126,7 +126,7 @@ def paint(
elif self.destination_socket is None:
pen = self._pen_dragging
else:
- pen = self.pens[self.run_color]
+ pen = self.pens[self.run_state]
painter.setPen(pen)
painter.setBrush(Qt.BrushStyle.NoBrush)
painter.drawPath(self.path())
@@ -161,12 +161,12 @@ def source(self, value: QPointF):
pass
@property
- def source_socket(self) -> OCBSocket:
+ def source_socket(self) -> Socket:
"""Source socket of the directed edge."""
return self._source_socket
@source_socket.setter
- def source_socket(self, value: OCBSocket):
+ def source_socket(self, value: Socket):
self._source_socket = value
if value is not None:
self.source_socket.add_edge(self, is_destination=False)
@@ -188,12 +188,12 @@ def destination(self, value: QPointF):
pass
@property
- def destination_socket(self) -> OCBSocket:
+ def destination_socket(self) -> Socket:
"""Destination socket of the directed edge."""
return self._destination_socket
@destination_socket.setter
- def destination_socket(self, value: OCBSocket):
+ def destination_socket(self, value: Socket):
self._destination_socket = value
if value is not None:
self.destination_socket.add_edge(self, is_destination=True)
@@ -259,12 +259,19 @@ def deserialize(self, data: OrderedDict, hashmap: dict = None, restore_id=True):
self.remove()
@property
- def run_color(self) -> int:
- """Run color"""
- return self._run_color
+ def run_state(self) -> int:
+ """Run state.
- @run_color.setter
- def run_color(self, value: int):
- self._run_color = value
+ Describe the current state of the Edge:
+ - 0: idle.
+ - 1: running.
+ - 2: transmitting.
+
+ """
+ return self._run_state
+
+ @run_state.setter
+ def run_state(self, value: int):
+ self._run_state = value
# Update to force repaint
self.update()
diff --git a/pyflow/core/kernel.py b/pyflow/core/kernel.py
index e4348466..c98be92f 100644
--- a/pyflow/core/kernel.py
+++ b/pyflow/core/kernel.py
@@ -1,4 +1,7 @@
-""" Module to create and manage ipython kernels """
+# Pyflow an open-source tool for modular visual programing in python
+# Copyright (C) 2021-2022 Bycelium
+
+""" Module to create and manage ipython kernels."""
import queue
from typing import Tuple
@@ -9,7 +12,7 @@
class Kernel:
- """jupyter_client kernel used to execute code and return output"""
+ """jupyter_client kernel used to execute code and return output."""
def __init__(self):
self.kernel_manager, self.client = start_new_kernel()
@@ -60,12 +63,12 @@ def run_block(self, block, code: str):
Also calls run_queue when finished
Args:
- block: OCBCodeBlock to send the output to
+ block: CodeBlock to send the output to
code: String representing a piece of Python code to execute
"""
worker = Worker(self, code)
# Change color to running
- block.run_color = 1
+ block.run_state = 1
worker.signals.stdout.connect(block.handle_stdout)
worker.signals.image.connect(block.handle_image)
worker.signals.finished.connect(self.run_queue)
@@ -74,9 +77,9 @@ def run_block(self, block, code: str):
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 == []:
+ if not self.execution_queue:
self.busy = False
return None
block, code = self.execution_queue.pop(0)
diff --git a/pyflow/core/pyeditor.py b/pyflow/core/pyeditor.py
index ce65dfa8..a3ccd676 100644
--- a/pyflow/core/pyeditor.py
+++ b/pyflow/core/pyeditor.py
@@ -1,7 +1,7 @@
# Pyflow an open-source tool for modular visual programing in python
-# Copyright (C) 2021 Mathïs FEDERICO
+# Copyright (C) 2021-2022 Bycelium
-""" Module for OCB in block python editor. """
+""" Module for the PyFlow python editor."""
from typing import TYPE_CHECKING, List
from PyQt5.QtCore import Qt
@@ -16,10 +16,10 @@
from PyQt5.Qsci import QsciScintilla, QsciLexerPython
from pyflow.graphics.theme_manager import theme_manager
-from pyflow.blocks.block import OCBBlock
+from pyflow.blocks.block import Block
if TYPE_CHECKING:
- from pyflow.graphics.view import OCBView
+ from pyflow.graphics.view import View
POINT_SIZE = 11
@@ -28,7 +28,7 @@ class PythonEditor(QsciScintilla):
"""In-block python editor for Pyflow."""
- def __init__(self, block: OCBBlock):
+ def __init__(self, block: Block):
"""In-block python editor for Pyflow.
Args:
@@ -67,7 +67,7 @@ def __init__(self, block: OCBBlock):
self.setWindowFlags(Qt.WindowType.FramelessWindowHint)
def update_theme(self):
- """Change the font and colors of the editor to match the current theme"""
+ """Change the font and colors of the editor to match the current theme."""
font = QFont()
font.setFamily(theme_manager().recommended_font_family)
font.setFixedPitch(True)
@@ -89,19 +89,19 @@ def update_theme(self):
lexer.setFont(font)
self.setLexer(lexer)
- def views(self) -> List["OCBView"]:
+ def views(self) -> List["View"]:
"""Get the views in which the python_editor is present."""
return self.block.scene().views()
def wheelEvent(self, event: QWheelEvent) -> None:
- """How PythonEditor handles wheel events"""
+ """How PythonEditor handles wheel events."""
if self.mode == "EDITING" and event.angleDelta().x() == 0:
event.accept()
return super().wheelEvent(event)
@property
def mode(self) -> int:
- """PythonEditor current mode"""
+ """PythonEditor current mode."""
return self._mode
@mode.setter
diff --git a/pyflow/core/serializable.py b/pyflow/core/serializable.py
index de75c58e..4c412f00 100644
--- a/pyflow/core/serializable.py
+++ b/pyflow/core/serializable.py
@@ -1,9 +1,9 @@
# Pyflow an open-source tool for modular visual programing in python
-# Copyright (C) 2021 Mathïs FEDERICO
+# Copyright (C) 2021-2022 Bycelium
""" Module for the Serializable base class """
-from typing import OrderedDict, Set
+from typing import Any, Dict, OrderedDict
class Serializable:
@@ -11,7 +11,7 @@ class Serializable:
"""Serializable base for serializable objects."""
MANDATORY_FIELDS: OrderedDict = {}
- DEFAULT_DATA: Set[str] = {}
+ DEFAULT_DATA: Dict[str, Any] = {}
def __init__(self):
self.id = id(self)
@@ -35,11 +35,11 @@ def deserialize(
raise NotImplementedError()
def complete_with_default(self, data: OrderedDict) -> None:
- """Add default data in place when fields are missing"""
+ """Add default data in place when fields are missing."""
for key in self.MANDATORY_FIELDS:
if key not in data:
raise ValueError(f"{key} of the socket is missing")
- for key in self.DEFAULT_DATA:
+ for key, val in self.DEFAULT_DATA.items():
if key not in data:
- data[key] = self.DEFAULT_DATA[key]
+ data[key] = val
diff --git a/pyflow/core/socket.py b/pyflow/core/socket.py
index 866ef168..a90e8eba 100644
--- a/pyflow/core/socket.py
+++ b/pyflow/core/socket.py
@@ -1,7 +1,7 @@
# Pyflow an open-source tool for modular visual programing in python
-# Copyright (C) 2021 Mathïs FEDERICO
+# Copyright (C) 2021-2022 Bycelium
-""" Module for OCB Sockets """
+""" Module for base Sockets."""
from __future__ import annotations
from typing import List, Optional, OrderedDict, TYPE_CHECKING
@@ -14,11 +14,11 @@
from pyflow.core.serializable import Serializable
if TYPE_CHECKING:
- from pyflow.core.edge import OCBEdge
- from pyflow.blocks.block import OCBBlock
+ from pyflow.core.edge import Edge
+ from pyflow.blocks.block import Block
-class OCBSocket(QGraphicsItem, Serializable):
+class Socket(QGraphicsItem, Serializable):
"""Base class for sockets in Pyflow."""
@@ -35,7 +35,7 @@ class OCBSocket(QGraphicsItem, Serializable):
def __init__(
self,
- block: "OCBBlock",
+ block: "Block",
socket_type: str = DEFAULT_DATA["type"],
flow_type: str = "exe",
radius: float = DEFAULT_DATA["metadata"]["radius"],
@@ -59,7 +59,7 @@ def __init__(
self.block = block
QGraphicsItem.__init__(self, parent=self.block)
- self.edges: List["OCBEdge"] = []
+ self.edges: List["Edge"] = []
self.socket_type = socket_type
self.flow_type = flow_type
@@ -75,7 +75,7 @@ def __init__(
"linecolor": linecolor,
}
- def add_edge(self, edge: "OCBEdge", is_destination: bool):
+ def add_edge(self, edge: "Edge", is_destination: bool):
"""Add a given edge to the socket edges."""
if not self._allow_multiple_edges:
for prev_edge in self.edges:
@@ -88,7 +88,7 @@ def add_edge(self, edge: "OCBEdge", is_destination: bool):
return
self.edges.append(edge)
- def remove_edge(self, edge: "OCBEdge"):
+ def remove_edge(self, edge: "Edge"):
"""Remove a given edge from the socket edges."""
self.edges.remove(edge)
diff --git a/pyflow/core/worker.py b/pyflow/core/worker.py
index f87ba6cc..60912843 100644
--- a/pyflow/core/worker.py
+++ b/pyflow/core/worker.py
@@ -1,7 +1,7 @@
# Pyflow an open-source tool for modular visual programing in python
-# Copyright (C) 2021 Mathïs FEDERICO
+# Copyright (C) 2021-2022 Bycelium
-""" Module to create and manage multi-threading workers """
+""" Module to create and manage multi-threading workers."""
import asyncio
from PyQt5.QtCore import QObject, pyqtSignal, QRunnable
@@ -18,7 +18,7 @@ class WorkerSignals(QObject):
class Worker(QRunnable):
- """Worker thread"""
+ """Worker thread."""
def __init__(self, kernel, code):
"""Initialize the worker object."""
@@ -29,7 +29,7 @@ def __init__(self, kernel, code):
self.signals = WorkerSignals()
async def run_code(self):
- """Run the code in the block"""
+ """Run the code in the block."""
# Execute the code
self.kernel.client.execute(self.code)
done = False
diff --git a/pyflow/graphics/__init__.py b/pyflow/graphics/__init__.py
index a474fac2..cbf0a267 100644
--- a/pyflow/graphics/__init__.py
+++ b/pyflow/graphics/__init__.py
@@ -1,2 +1,2 @@
# Pyflow an open-source tool for modular visual programing in python
-# Copyright (C) 2021 Mathïs FEDERICO
+# Copyright (C) 2021-2022 Bycelium
diff --git a/pyflow/graphics/theme.py b/pyflow/graphics/theme.py
index 30182858..7d3d15c5 100644
--- a/pyflow/graphics/theme.py
+++ b/pyflow/graphics/theme.py
@@ -1,6 +1,10 @@
-"""
-This module defined Theme, a class that
-contains the details of the theme
+# Pyflow an open-source tool for modular visual programing in python
+# Copyright (C) 2021-2022 Bycelium
+
+""" Module for Theme.
+
+Theme is a class that contains the details of the coloring theme.
+
"""
import json
@@ -9,7 +13,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 = "{}"):
"""
@@ -33,7 +37,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"))
diff --git a/pyflow/graphics/theme_manager.py b/pyflow/graphics/theme_manager.py
index d967a76f..5b3c18d0 100644
--- a/pyflow/graphics/theme_manager.py
+++ b/pyflow/graphics/theme_manager.py
@@ -1,4 +1,8 @@
-"""
+# Pyflow an open-source tool for modular visual programing in python
+# Copyright (C) 2021-2022 Bycelium
+
+""" Module for the ThemeManager.
+
This module provides `theme_manager()`,
a method that returns a handle to the theme manager of the application.
@@ -6,21 +10,25 @@
of the text areas containing code.
"""
import os
+import pathlib
from typing import List
from PyQt5.QtGui import QFontDatabase
from PyQt5.QtCore import pyqtSignal, QObject
from pyflow.graphics.theme import Theme
+from pyflow import __file__ as INIT_PATH
+
+PACKAGE_PATH = pathlib.Path(INIT_PATH).parent
class ThemeManager(QObject):
- """Class loading theme files and providing the options set in those files"""
+ """Class loading theme files and providing the options set in those files."""
themeChanged = pyqtSignal()
def __init__(self, parent=None):
- """Load the default themes and the fonts available to construct the ThemeManager"""
+ """Load the default themes and the fonts available to construct the ThemeManager."""
super().__init__(parent)
self._preferred_fonts = ["Inconsolata", "Roboto Mono", "Courier"]
self.recommended_font_family = "Monospace"
@@ -33,7 +41,7 @@ def __init__(self, parent=None):
self._themes = []
self._selected_theme_index = 0
- theme_path = "./themes"
+ theme_path = os.path.join(PACKAGE_PATH, "themes")
theme_paths = os.listdir(theme_path)
for p in theme_paths:
full_path = os.path.join(theme_path, p)
@@ -45,7 +53,7 @@ def __init__(self, parent=None):
@property
def selected_theme_index(self):
- """Return the index of the selected theme"""
+ """Return the index of the selected theme."""
return self._selected_theme_index
@selected_theme_index.setter
@@ -54,11 +62,11 @@ def selected_theme_index(self, value: int):
self.themeChanged.emit()
def list_themes(self) -> List[str]:
- """List the themes"""
+ """List the themes."""
return [theme.name for theme in self._themes]
def current_theme(self) -> Theme:
- """Return the current theme"""
+ """Return the current theme."""
return self._themes[self.selected_theme_index]
@@ -66,7 +74,7 @@ def current_theme(self) -> Theme:
def theme_manager():
- """Retreive the theme manager of the application"""
+ """Retreive the theme manager of the application."""
global theme_handle
if theme_handle is None:
theme_handle = ThemeManager()
diff --git a/pyflow/graphics/view.py b/pyflow/graphics/view.py
index d706644e..97f31cad 100644
--- a/pyflow/graphics/view.py
+++ b/pyflow/graphics/view.py
@@ -1,10 +1,11 @@
# Pyflow an open-source tool for modular visual programing in python
-# Copyright (C) 2021 Mathïs FEDERICO
+# Copyright (C) 2021-2022 Bycelium
-""" Module for the OCB View """
+""" Module for View."""
import json
import os
+import pathlib
from typing import List, Tuple
from PyQt5.QtCore import QEvent, QPoint, QPointF, Qt
@@ -13,18 +14,22 @@
from PyQt5.sip import isdeleted
-from pyflow.scene import OCBScene
-from pyflow.core.socket import OCBSocket
-from pyflow.core.edge import OCBEdge
-from pyflow.blocks.block import OCBBlock
-from pyflow.blocks.codeblock import OCBCodeBlock
+from pyflow.scene import Scene
+from pyflow.core.socket import Socket
+from pyflow.core.edge import Edge
+from pyflow.blocks.block import Block
+from pyflow.blocks.codeblock import CodeBlock
+from pyflow.blocks import __file__ as BLOCK_INIT_PATH
+
+BLOCK_PATH = pathlib.Path(BLOCK_INIT_PATH).parent
+BLOCKFILES_PATH = os.path.join(BLOCK_PATH, "blockfiles")
EPS: float = 1e-10 # To check if blocks are of size 0
-class OCBView(QGraphicsView):
+class View(QGraphicsView):
- """View for the OCB Window."""
+ """View for the Window."""
MODE_NOOP = 0
MODE_EDGE_DRAG = 1
@@ -38,7 +43,7 @@ class OCBView(QGraphicsView):
def __init__(
self,
- scene: OCBScene,
+ scene: Scene,
parent=None,
zoom_step: float = 1.25,
zoom_min: float = 0.2,
@@ -57,7 +62,7 @@ def __init__(
self.setScene(scene)
def init_ui(self):
- """Initialize the custom OCB View UI."""
+ """Initialize the custom View UI."""
# Antialiasing
self.setRenderHints(
QPainter.RenderHint.Antialiasing
@@ -75,8 +80,8 @@ def init_ui(self):
# Selection box
self.setDragMode(QGraphicsView.DragMode.RubberBandDrag)
- def scene(self) -> OCBScene:
- """Get current OCBScene."""
+ def scene(self) -> Scene:
+ """Get current Scene."""
return super().scene()
def mousePressEvent(self, event: QMouseEvent):
@@ -89,7 +94,7 @@ def mousePressEvent(self, event: QMouseEvent):
super().mousePressEvent(event)
def mouseReleaseEvent(self, event: QMouseEvent):
- """Dispatch Qt's mouseRelease events to corresponding functions below"""
+ """Dispatch Qt's mouseRelease events to corresponding functions below."""
if event.button() == Qt.MouseButton.MiddleButton:
self.middleMouseButtonRelease(event)
elif event.button() == Qt.MouseButton.LeftButton:
@@ -98,23 +103,23 @@ def mouseReleaseEvent(self, event: QMouseEvent):
super().mouseReleaseEvent(event)
def mouseMoveEvent(self, event: QMouseEvent) -> None:
- """OCBView reaction to mouseMoveEvent."""
+ """View reaction to mouseMoveEvent."""
self.lastMousePos = self.mapToScene(event.pos())
self.drag_edge(event, "move")
if event is not None:
super().mouseMoveEvent(event)
def leftMouseButtonPress(self, event: QMouseEvent):
- """OCBView reaction to leftMouseButtonPress event."""
+ """View reaction to leftMouseButtonPress event."""
# If clicked on a block, bring it forward.
item_at_click = self.itemAt(event.pos())
if item_at_click is not None:
while item_at_click.parentItem() is not None:
- if isinstance(item_at_click, OCBBlock):
+ if isinstance(item_at_click, Block):
break
item_at_click = item_at_click.parentItem()
- if isinstance(item_at_click, OCBBlock):
+ if isinstance(item_at_click, Block):
self.bring_block_forward(item_at_click)
# If clicked on a socket, start dragging an edge.
@@ -123,19 +128,19 @@ def leftMouseButtonPress(self, event: QMouseEvent):
super().mousePressEvent(event)
def leftMouseButtonRelease(self, event: QMouseEvent):
- """OCBView reaction to leftMouseButtonRelease event."""
+ """View reaction to leftMouseButtonRelease event."""
event = self.drag_edge(event, "release")
if event is not None:
super().mouseReleaseEvent(event)
def middleMouseButtonPress(self, event: QMouseEvent):
- """OCBView reaction to middleMouseButtonPress event."""
+ """View reaction to middleMouseButtonPress event."""
if self.itemAt(event.pos()) is None:
event = self.drag_scene(event, "press")
super().mousePressEvent(event)
def middleMouseButtonRelease(self, event: QMouseEvent):
- """OCBView reaction to middleMouseButtonRelease event."""
+ """View reaction to middleMouseButtonRelease event."""
event = self.drag_scene(event, "release")
super().mouseReleaseEvent(event)
self.setDragMode(QGraphicsView.DragMode.RubberBandDrag)
@@ -166,9 +171,9 @@ def moveToItems(self) -> bool:
if len(self.scene().selectedItems()) > 0:
items = self.scene().selectedItems()
- code_blocks: List[OCBBlock] = [i for i in items if isinstance(i, OCBBlock)]
+ code_blocks: List[Block] = [i for i in items if isinstance(i, Block)]
- if len(code_blocks) == 0:
+ if not code_blocks:
return False
# Get the blocks with min and max x and y coordinates
@@ -196,7 +201,7 @@ def moveToItems(self) -> bool:
return True
def getDistanceToCenter(self, x: float, y: float) -> Tuple[float]:
- """Return the vector from the (x,y) position given to the center of the view"""
+ """Return the vector from the (x,y) position given to the center of the view."""
ypos = self.verticalScrollBar().value()
xpos = self.horizontalScrollBar().value()
return (
@@ -206,13 +211,13 @@ def getDistanceToCenter(self, x: float, y: float) -> Tuple[float]:
def moveViewOnArrow(self, event: QKeyEvent) -> bool:
"""
- OCBView reaction to an arrow key being pressed.
+ View reaction to an arrow key being pressed.
Returns True if the event was handled.
"""
# The focusItem has priority for this event if it is a source editor
if self.scene().focusItem() is not None:
parent = self.scene().focusItem().parentItem()
- if isinstance(parent, OCBCodeBlock) and parent.source_editor.hasFocus():
+ if isinstance(parent, CodeBlock) and parent.source_editor.hasFocus():
return False
n_selected_items = len(self.scene().selectedItems())
@@ -222,13 +227,11 @@ def moveViewOnArrow(self, event: QKeyEvent) -> bool:
code_blocks = [
i
for i in self.scene().items()
- if isinstance(i, OCBBlock) and not i.isSelected()
+ if isinstance(i, Block) and not i.isSelected()
]
reference = None
- if n_selected_items == 1 and isinstance(
- self.scene().selectedItems()[0], OCBBlock
- ):
+ if n_selected_items == 1 and isinstance(self.scene().selectedItems()[0], Block):
selected_item = self.scene().selectedItems()[0]
reference = QPoint(
selected_item.x() + selected_item.width / 2,
@@ -262,7 +265,7 @@ def in_region(x, y, key):
key_id = event.key()
dist_array = filter(lambda pos: in_region(pos[2], pos[3], key_id), dist_array)
dist_array = list(dist_array)
- if len(dist_array) == 0:
+ if not dist_array:
return False
def oriented_distance(x, y, key):
@@ -277,14 +280,14 @@ def oriented_distance(x, y, key):
item_to_navigate = self.scene().itemAt(
block_center_x, block_center_y, self.transform()
)
- if isinstance(item_to_navigate.parentItem(), OCBBlock):
+ if isinstance(item_to_navigate.parentItem(), Block):
item_to_navigate.parentItem().setSelected(True)
self.centerView(block_center_x, block_center_y)
return True
def keyPressEvent(self, event: QKeyEvent):
- """OCBView reaction to a key being pressed"""
+ """View reaction to a key being pressed."""
key_id = event.key()
if key_id in [
Qt.Key.Key_Up,
@@ -299,10 +302,10 @@ def keyPressEvent(self, event: QKeyEvent):
def retreiveBlockTypes(self) -> List[Tuple[str]]:
"""Retreive the list of stored blocks."""
- block_type_files = os.listdir("blocks")
+ block_type_files = os.listdir(BLOCKFILES_PATH)
block_types = []
- for b in block_type_files:
- filepath = os.path.join("blocks", b)
+ for blockfile_name in block_type_files:
+ filepath = os.path.join(BLOCKFILES_PATH, blockfile_name)
with open(filepath, "r", encoding="utf-8") as file:
data = json.loads(file.read())
title = "New Block"
@@ -315,7 +318,7 @@ def retreiveBlockTypes(self) -> List[Tuple[str]]:
return block_types
def contextMenuEvent(self, event: QContextMenuEvent):
- """Displays the context menu when inside a view"""
+ """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():
@@ -349,7 +352,7 @@ def wheelEvent(self, event: QWheelEvent):
super().wheelEvent(event)
def setZoom(self, new_zoom: float):
- """Set the zoom to the appropriate level"""
+ """Set the zoom to the appropriate level."""
zoom_factor = new_zoom / self.zoom
self.scale(zoom_factor, zoom_factor)
self.zoom = new_zoom
@@ -361,7 +364,7 @@ def deleteSelected(self):
selected_item.remove()
scene.history.checkpoint("Delete selected elements", set_modified=True)
- def bring_block_forward(self, block: OCBBlock):
+ def bring_block_forward(self, block: Block):
"""Move the selected block in front of other blocks.
Args:
@@ -411,12 +414,12 @@ def drag_edge(self, event: QMouseEvent, action="press"):
scene = self.scene()
if action == "press":
if (
- isinstance(item_at_click, OCBSocket)
+ isinstance(item_at_click, Socket)
and self.mode != self.MODE_EDGE_DRAG
and item_at_click.socket_type != "input"
):
self.mode = self.MODE_EDGE_DRAG
- self.edge_drag = OCBEdge(
+ self.edge_drag = Edge(
source_socket=item_at_click,
destination=self.mapToScene(event.pos()),
)
@@ -425,7 +428,7 @@ def drag_edge(self, event: QMouseEvent, action="press"):
elif action == "release":
if self.mode == self.MODE_EDGE_DRAG:
if (
- isinstance(item_at_click, OCBSocket)
+ isinstance(item_at_click, Socket)
and item_at_click is not self.edge_drag.source_socket
and item_at_click.socket_type != "output"
):
diff --git a/pyflow/graphics/widget.py b/pyflow/graphics/widget.py
index db89e000..f593c8ca 100644
--- a/pyflow/graphics/widget.py
+++ b/pyflow/graphics/widget.py
@@ -1,20 +1,20 @@
# Pyflow an open-source tool for modular visual programing in python
-# Copyright (C) 2021 Mathïs FEDERICO
+# Copyright (C) 2021-2022 Bycelium
-""" Module for the OCB Widget """
+""" Module for the base PyFlow Widget."""
import os
from PyQt5.QtWidgets import QVBoxLayout, QWidget
from PyQt5.QtCore import Qt
-from pyflow.scene import OCBScene
-from pyflow.graphics.view import OCBView
+from pyflow.scene import Scene
+from pyflow.graphics.view import View
-class OCBWidget(QWidget):
+class Widget(QWidget):
- """Window for the OCB application."""
+ """Window for a graph visualisation."""
def __init__(self, parent=None):
super().__init__(parent)
@@ -25,11 +25,11 @@ def __init__(self, parent=None):
self.setLayout(self.layout)
# Graphics Scene
- self.scene = OCBScene()
+ self.scene = Scene()
self.scene.addHasBeenModifiedListener(self.updateTitle)
# Graphics View
- self.view = OCBView(self.scene)
+ self.view = View(self.scene)
self.layout.addWidget(self.view)
self.savepath = None
@@ -63,7 +63,7 @@ def save(self):
self.scene.save(self.savepath)
def saveAsJupyter(self, filepath: str):
- """Save the current graph notebook as a regular python notebook"""
+ """Save the current graph notebook as a regular python notebook."""
self.scene.save_to_ipynb(filepath)
def load(self, filepath: str):
diff --git a/pyflow/graphics/window.py b/pyflow/graphics/window.py
index 9ca5559d..14d94cab 100644
--- a/pyflow/graphics/window.py
+++ b/pyflow/graphics/window.py
@@ -1,13 +1,14 @@
# Pyflow an open-source tool for modular visual programing in python
-# Copyright (C) 2021 Mathïs FEDERICO
-# pylint:disable=too-many-instance-attributes
+# Copyright (C) 2021-2022 Bycelium
+# pylint:disable=too-many-instance-attributes, unsubscriptable-object
-""" Module for the OCB Window """
+""" Module for the base Window."""
import os
+import pathlib
+
from PyQt5.QtCore import QPoint, QSettings, QSize, Qt, QSignalMapper
from PyQt5.QtGui import QCloseEvent, QKeySequence
-
from PyQt5.QtWidgets import (
QWidget,
QAction,
@@ -17,28 +18,27 @@
QMdiArea,
)
-from pyflow.graphics.widget import OCBWidget
+from pyflow.graphics.widget import Widget
from pyflow.graphics.theme_manager import theme_manager
from pyflow.qss import loadStylesheets
+from pyflow.qss import __file__ as QSS_INIT_PATH
+
+QSS_PATH = pathlib.Path(QSS_INIT_PATH).parent
-class OCBWindow(QMainWindow):
+class Window(QMainWindow):
"""Main window of the Pyflow Qt-based application."""
def __init__(self):
super().__init__()
- self.stylesheet_filename = os.path.join(
- os.path.dirname(__file__), "..", "qss", "ocb.qss"
- )
- loadStylesheets(
- (
- os.path.join(os.path.dirname(__file__), "..", "qss", "ocb_dark.qss"),
- self.stylesheet_filename,
- )
- )
+ self.stylesheets = [
+ os.path.join(QSS_PATH, "pyflow.qss"),
+ os.path.join(QSS_PATH, "pyflow_dark.qss"),
+ ]
+ loadStylesheets(self.stylesheets)
self.mdiArea = QMdiArea()
self.mdiArea.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAsNeeded)
@@ -278,12 +278,12 @@ def updateWindowMenu(self):
def createNewMdiChild(self, filename: str = None):
"""Create a new graph subwindow loading a file if a path is given."""
- ocb_widget = OCBWidget()
+ _widget = Widget()
if filename is not None:
- ocb_widget.scene.load(filename)
+ _widget.scene.load(filename)
if filename.split(".")[-1] == "ipyg":
- ocb_widget.savepath = filename
- return self.mdiArea.addSubWindow(ocb_widget)
+ _widget.savepath = filename
+ return self.mdiArea.addSubWindow(_widget)
def onFileNew(self):
"""Create a new file."""
@@ -362,15 +362,15 @@ def onFileSaveAsJupyter(self) -> bool:
return True
return False
- def saveWindow(self, window: OCBWidget):
- """Save the given window"""
+ def saveWindow(self, window: Widget):
+ """Save the given window."""
window.save()
self.statusbar.showMessage(
f"Successfully saved ipygraph at {window.savepath}", 2000
)
@staticmethod
- def is_not_editing(current_window: OCBWidget):
+ def is_not_editing(current_window: Widget):
"""True if current_window exists and is not in editing mode."""
return current_window is not None and not current_window.view.is_mode("EDITING")
@@ -451,8 +451,8 @@ def maybeSave(self) -> bool:
return True
return False
- def activeMdiChild(self) -> OCBWidget:
- """Get the active OCBWidget if existing."""
+ def activeMdiChild(self) -> Widget:
+ """Get the active Widget if existing."""
activeSubWindow = self.mdiArea.activeSubWindow()
if activeSubWindow is not None:
return activeSubWindow.widget()
@@ -486,7 +486,7 @@ def onMoveToItems(self):
If items are selected, then make all the selected items visible instead
"""
current_window = self.activeMdiChild()
- if current_window is not None and isinstance(current_window, OCBWidget):
+ if current_window is not None and isinstance(current_window, Widget):
current_window.moveToItems()
def setTheme(self, theme_index):
diff --git a/pyflow/qss/__init__.py b/pyflow/qss/__init__.py
index 8b1d3ea3..edd63e00 100644
--- a/pyflow/qss/__init__.py
+++ b/pyflow/qss/__init__.py
@@ -1,7 +1,7 @@
# Pyflow an open-source tool for modular visual programing in python
-# Copyright (C) 2021 Mathïs FEDERICO
+# Copyright (C) 2021-2022 Bycelium
-""" Module for the OCB qss and styles. """
+""" Module for qss and styles. """
from typing import List
diff --git a/pyflow/qss/dark_resources.py b/pyflow/qss/dark_resources.py
index 9a1f60f9..699e3ff7 100644
--- a/pyflow/qss/dark_resources.py
+++ b/pyflow/qss/dark_resources.py
@@ -8,7 +8,7 @@
WARNING! All changes made in this file will be lost!
"""
-from qtpy import QtCore
+from PyQt5 import QtCore
qt_resource_data = b"\
\x00\x00\x05\x1a\
diff --git a/pyflow/qss/ocb.qss b/pyflow/qss/pyflow.qss
similarity index 100%
rename from pyflow/qss/ocb.qss
rename to pyflow/qss/pyflow.qss
diff --git a/pyflow/qss/ocb_dark.qss b/pyflow/qss/pyflow_dark.qss
similarity index 100%
rename from pyflow/qss/ocb_dark.qss
rename to pyflow/qss/pyflow_dark.qss
diff --git a/pyflow/scene/__init__.py b/pyflow/scene/__init__.py
index 593113ba..8498c9ec 100644
--- a/pyflow/scene/__init__.py
+++ b/pyflow/scene/__init__.py
@@ -1,6 +1,6 @@
# Pyflow an open-source tool for modular visual programing in python
-# Copyright (C) 2021 Mathïs FEDERICO
+# Copyright (C) 2021-2022 Bycelium
-""" Module for the OCBScene creation and manipulations. """
+""" Module for the Scene creation and manipulations."""
-from pyflow.scene.scene import OCBScene
+from pyflow.scene.scene import Scene
diff --git a/pyflow/scene/clipboard.py b/pyflow/scene/clipboard.py
index 85bfb210..7fbf606a 100644
--- a/pyflow/scene/clipboard.py
+++ b/pyflow/scene/clipboard.py
@@ -1,7 +1,7 @@
# Pyflow an open-source tool for modular visual programing in python
-# Copyright (C) 2021 Mathïs FEDERICO
+# Copyright (C) 2021-2022 Bycelium
-""" Module for the handling an OCBScene clipboard operations. """
+""" Module for the handling of scene clipboard operations. """
from typing import TYPE_CHECKING, OrderedDict
from warnings import warn
@@ -9,19 +9,19 @@
import json
from PyQt5.QtWidgets import QApplication
-from pyflow.core.edge import OCBEdge
+from pyflow.core.edge import Edge
if TYPE_CHECKING:
- from pyflow.scene import OCBScene
- from pyflow.graphics.view import OCBView
+ from pyflow.scene import Scene
+ from pyflow.graphics.view import View
class SceneClipboard:
- """Helper object to handle clipboard operations on an OCBScene."""
+ """Helper object to handle clipboard operations on an Scene."""
- def __init__(self, scene: "OCBScene"):
- """Helper object to handle clipboard operations on an OCBScene.
+ def __init__(self, scene: "Scene"):
+ """Helper object to handle clipboard operations on an Scene.
Args:
scene: Scene reference.
@@ -111,7 +111,7 @@ def _deserializeData(self, data: OrderedDict, set_selected=True):
# Create edges
for edge_data in data["edges"]:
- edge = OCBEdge()
+ edge = Edge()
edge.deserialize(edge_data, hashmap, restore_id=False)
if set_selected:
diff --git a/pyflow/scene/from_ipynb_conversion.py b/pyflow/scene/from_ipynb_conversion.py
index 3f918056..a6d17781 100644
--- a/pyflow/scene/from_ipynb_conversion.py
+++ b/pyflow/scene/from_ipynb_conversion.py
@@ -1,4 +1,7 @@
-""" Module for converting ipynb data to ipyg data """
+# Pyflow an open-source tool for modular visual programing in python
+# Copyright (C) 2021-2022 Bycelium
+
+""" Module for converting notebook (.ipynb) data to pygraph (.ipyg) data."""
from typing import OrderedDict, List
@@ -118,12 +121,12 @@ def get_blocks_data(
def is_title(block_data: OrderedDict) -> bool:
- """Checks if the block is a one-line markdown block which could correspond to a title"""
+ """Checks if the block is a one-line markdown block which could correspond to a title."""
if block_data["block_type"] != BLOCK_TYPE_TO_NAME["markdown"]:
return False
if "\n" in block_data["text"]:
return False
- if len(block_data["text"]) == 0 or len(block_data["text"]) > TITLE_MAX_LENGTH:
+ if not block_data["text"] or len(block_data["text"]) > TITLE_MAX_LENGTH:
return False
# Headings, quotes, bold or italic text are not considered to be headings
if block_data["text"][0] in {"#", "*", "`"}:
@@ -154,7 +157,7 @@ def adujst_markdown_blocks_width(blocks_data: OrderedDict) -> None:
def get_edges_data(blocks_data: OrderedDict) -> OrderedDict:
- """Add sockets to the blocks (in place) and returns the edge list"""
+ """Add sockets to the blocks (in place) and returns the edge list."""
code_blocks: List[OrderedDict] = [
block
for block in blocks_data
@@ -186,7 +189,7 @@ def get_edges_data(blocks_data: OrderedDict) -> OrderedDict:
def get_input_socket_data(socket_id: int) -> OrderedDict:
- """Returns the input socket's data with the corresponding id"""
+ """Returns the input socket's data with the corresponding id."""
return {
"id": socket_id,
"type": "input",
@@ -213,7 +216,7 @@ def get_edge_data(
edge_end_block_id: int,
edge_end_socket_id: int,
) -> OrderedDict:
- """Return the ordered dict corresponding to the given parameters"""
+ """Return the ordered dict corresponding to the given parameters."""
return {
"id": edge_id,
"source": {"block": edge_start_block_id, "socket": edge_start_socket_id},
diff --git a/pyflow/scene/history.py b/pyflow/scene/history.py
index 676c3cd1..6f26ce7d 100644
--- a/pyflow/scene/history.py
+++ b/pyflow/scene/history.py
@@ -1,16 +1,16 @@
# Pyflow an open-source tool for modular visual programing in python
-# Copyright (C) 2021 Mathïs FEDERICO
+# Copyright (C) 2021-2022 Bycelium
-""" Module for the handling an OCBScene history. """
+""" Module for the handling a scene history. """
from typing import TYPE_CHECKING, Any
if TYPE_CHECKING:
- from pyflow.scene import OCBScene
+ from pyflow.scene import Scene
class SceneHistory:
- """Helper object to handle undo/redo operations on an OCBScene.
+ """Helper object to handle undo/redo operations on an Scene.
Args:
scene: Scene reference.
@@ -18,7 +18,7 @@ class SceneHistory:
"""
- def __init__(self, scene: "OCBScene", max_stack: int = 50):
+ def __init__(self, scene: "Scene", max_stack: int = 50):
self.scene = scene
self.history_stack = []
self.current = -1
diff --git a/pyflow/scene/ipynb_conversion_constants.py b/pyflow/scene/ipynb_conversion_constants.py
index 65880903..93baa6fe 100644
--- a/pyflow/scene/ipynb_conversion_constants.py
+++ b/pyflow/scene/ipynb_conversion_constants.py
@@ -1,4 +1,7 @@
-""" Module with the constants used to converter to ipynb and from ipynb """
+# Pyflow an open-source tool for modular visual programing in python
+# Copyright (C) 2021-2022 Bycelium
+
+""" Module with the constants used to convert to and from notebooks."""
from typing import Dict
@@ -15,11 +18,11 @@
DEFAULT_TEXT_WIDTH = 618
BLOCK_TYPE_TO_NAME: Dict[str, str] = {
- "code": "OCBCodeBlock",
- "markdown": "OCBMarkdownBlock",
+ "code": "CodeBlock",
+ "markdown": "MarkdownBlock",
}
-BLOCK_TYPE_SUPPORTED_FOR_IPYG_TO_IPYNB = {"OCBCodeBlock", "OCBMarkdownBlock"}
+BLOCK_TYPE_SUPPORTED_FOR_IPYG_TO_IPYNB = {"CodeBlock", "MarkdownBlock"}
DEFAULT_NOTEBOOK_DATA = {
"cells": [],
diff --git a/pyflow/scene/scene.py b/pyflow/scene/scene.py
index 9ee8fe6d..e7d08fc2 100644
--- a/pyflow/scene/scene.py
+++ b/pyflow/scene/scene.py
@@ -1,7 +1,7 @@
# Pyflow an open-source tool for modular visual programing in python
-# Copyright (C) 2021 Mathïs FEDERICO
+# Copyright (C) 2021-2022 Bycelium
-""" Module for the OCB Scene """
+""" Module for the base Scene."""
import math
import json
@@ -14,8 +14,8 @@
from PyQt5.QtWidgets import QGraphicsScene
from pyflow.core.serializable import Serializable
-from pyflow.blocks.block import OCBBlock
-from pyflow.core.edge import OCBEdge
+from pyflow.blocks.block import Block
+from pyflow.core.edge import Edge
from pyflow.scene.clipboard import SceneClipboard
from pyflow.scene.history import SceneHistory
from pyflow.core.kernel import Kernel
@@ -24,9 +24,9 @@
from pyflow import blocks
-class OCBScene(QGraphicsScene, Serializable):
+class Scene(QGraphicsScene, Serializable):
- """Scene for the OCB Window."""
+ """Scene for the Window."""
def __init__(
self,
@@ -76,23 +76,23 @@ def addHasBeenModifiedListener(self, callback: FunctionType):
"""Add a callback that will trigger when the scene has been modified."""
self._has_been_modified_listeners.append(callback)
- def sortedSelectedItems(self) -> List[Union[OCBBlock, OCBEdge]]:
+ def sortedSelectedItems(self) -> List[Union[Block, Edge]]:
"""Returns the selected blocks and selected edges in two separate lists."""
selected_blocks, selected_edges = [], []
for item in self.selectedItems():
- if isinstance(item, OCBBlock):
+ if isinstance(item, Block):
selected_blocks.append(item)
- if isinstance(item, OCBEdge):
+ if isinstance(item, Edge):
selected_edges.append(item)
return selected_blocks, selected_edges
def drawBackground(self, painter: QPainter, rect: QRectF):
- """Draw the Scene background"""
+ """Draw the Scene background."""
super().drawBackground(painter, rect)
self.drawGrid(painter, rect)
def drawGrid(self, painter: QPainter, rect: QRectF):
- """Draw the background grid"""
+ """Draw the background grid."""
left = int(math.floor(rect.left()))
top = int(math.floor(rect.top()))
right = int(math.ceil(rect.right()))
@@ -144,7 +144,7 @@ def save_to_ipyg(self, filepath: str):
file.write(json.dumps(self.serialize(), indent=4))
def save_to_ipynb(self, filepath: str):
- """Save the scene into filepath as ipynb"""
+ """Save the scene into filepath as ipynb."""
if "." not in filepath:
filepath += ".ipynb"
@@ -204,9 +204,9 @@ def serialize(self) -> OrderedDict:
blocks = []
edges = []
for item in self.items():
- if isinstance(item, OCBBlock):
+ if isinstance(item, Block):
blocks.append(item)
- elif isinstance(item, OCBEdge):
+ elif isinstance(item, Edge):
edges.append(item)
blocks.sort(key=lambda x: x.id)
edges.sort(key=lambda x: x.id)
@@ -219,7 +219,7 @@ def serialize(self) -> OrderedDict:
)
def create_block_from_file(self, filepath: str, x: float = 0, y: float = 0):
- """Create a new block from a .ocbb file"""
+ """Create a new block from a .b file."""
with open(filepath, "r", encoding="utf-8") as file:
data = json.loads(file.read())
data["position"] = [x, y]
@@ -228,8 +228,8 @@ def create_block_from_file(self, filepath: str, x: float = 0, y: float = 0):
def create_block(
self, data: OrderedDict, hashmap: dict = None, restore_id: bool = True
- ) -> OCBBlock:
- """Create a new block from an OrderedDict"""
+ ) -> Block:
+ """Create a new block from an OrderedDict."""
block = None
@@ -266,7 +266,7 @@ def deserialize(
# Create edges
for edge_data in data["edges"]:
- edge = OCBEdge()
+ edge = Edge()
edge.deserialize(edge_data, hashmap, restore_id)
self.addItem(edge)
hashmap.update({edge_data["id"]: edge})
diff --git a/pyflow/scene/to_ipynb_conversion.py b/pyflow/scene/to_ipynb_conversion.py
index a079cf99..8cfb6c57 100644
--- a/pyflow/scene/to_ipynb_conversion.py
+++ b/pyflow/scene/to_ipynb_conversion.py
@@ -1,4 +1,7 @@
-""" Module for converting ipyg data to ipynb data """
+# Pyflow an open-source tool for modular visual programing in python
+# Copyright (C) 2021-2022 Bycelium
+
+""" Module for converting pygraph (.ipyg) data to notebook (.ipynb) data."""
from typing import OrderedDict, List
@@ -8,7 +11,7 @@
def ipyg_to_ipynb(data: OrderedDict) -> OrderedDict:
- """Convert ipyg data (as ordered dict) into ipynb data (as ordered dict)"""
+ """Convert ipyg data (as ordered dict) into ipynb data (as ordered dict)."""
ordered_data: OrderedDict = get_block_in_order(data)
ipynb_data: OrderedDict = copy.deepcopy(DEFAULT_NOTEBOOK_DATA)
@@ -21,14 +24,14 @@ def ipyg_to_ipynb(data: OrderedDict) -> OrderedDict:
def get_block_in_order(data: OrderedDict) -> OrderedDict:
- """Changes the order of the blocks from random to the naturel flow of the text"""
+ """Changes the order of the blocks from random to the naturel flow of the text."""
# Not implemented yet
return data
def block_to_ipynb_cell(block_data: OrderedDict) -> OrderedDict:
- """Convert a ipyg block into its corresponding ipynb cell"""
+ """Convert a ipyg block into its corresponding ipynb cell."""
if block_data["block_type"] == BLOCK_TYPE_TO_NAME["code"]:
cell_data: OrderedDict = copy.deepcopy(DEFAULT_CODE_CELL)
cell_data["source"] = split_lines_and_add_newline(block_data["source"])
@@ -45,7 +48,7 @@ def block_to_ipynb_cell(block_data: OrderedDict) -> OrderedDict:
def split_lines_and_add_newline(text: str) -> List[str]:
"""Split the text and add a \\n at the end of each line
- This is the jupyter notebook default formatting for source, outputs and text"""
+ This is the jupyter notebook default formatting for source, outputs and text."""
lines = text.split("\n")
for i in range(len(lines) - 1):
lines[i] += "\n"
diff --git a/themes/default.theme b/pyflow/themes/default.theme
similarity index 100%
rename from themes/default.theme
rename to pyflow/themes/default.theme
diff --git a/themes/monokai.theme b/pyflow/themes/monokai.theme
similarity index 100%
rename from themes/monokai.theme
rename to pyflow/themes/monokai.theme
diff --git a/pylint_score.py b/pylint_score.py
index b983c8e9..1c7aee7a 100644
--- a/pylint_score.py
+++ b/pylint_score.py
@@ -1,7 +1,7 @@
# Pyflow an open-source tool for modular visual programing in python
-# Copyright (C) 2021 Mathïs FEDERICO
+# Copyright (C) 2021-2022 Bycelium
-""" Module to get pylint score. """
+""" Module to get pylint score."""
import sys
import html
diff --git a/pypi-readme.md b/pypi-readme.md
new file mode 100644
index 00000000..db26cedd
--- /dev/null
+++ b/pypi-readme.md
@@ -0,0 +1,55 @@
+# PyFlow
+
+[![Pytest badge](https://github.com/Bycelium/PyFlow/actions/workflows/python-tests.yml/badge.svg?branch=master)](https://github.com/Bycelium/PyFlow/actions/workflows/python-tests.yml)
+[![Codacy Badge](https://app.codacy.com/project/badge/Grade/9874915d70e440418447f371c4bd5061)](https://www.codacy.com/gh/Bycelium/PyFlow/dashboard?utm_source=github.com&utm_medium=referral&utm_content=Bycelium/PyFlow&utm_campaign=Badge_Grade)
+[![Pylint badge](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FMathisFederico%2F00ce73155619a4544884ca6d251954b3%2Fraw%2Fopencodeblocks_pylint_badge.json)](https://github.com/Bycelium/PyFlow/actions/workflows/python-pylint.yml)
+[![Codacy Badge](https://app.codacy.com/project/badge/Coverage/9874915d70e440418447f371c4bd5061)](https://www.codacy.com/gh/Bycelium/PyFlow/dashboard?utm_source=github.com&utm_medium=referral&utm_content=Bycelium/PyFlow&utm_campaign=Badge_Coverage)
+[![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/Bycelium/PyFlow/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/Bycelium/PyFlow/actions/workflows/python-coverage.yml)
+[![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)
+
+PyFlow is an open-source graph-structured interactive Python development tool
+
+Although for now Pyflow is in closed Beta and features are coming in bit by bit, stay tuned for the first release soon !
+
+![](media/mnist_example.gif)
+
+## Community
+
+Join our [Discord](https://discord.gg/xZq8Tp4srd) to beta-test features, share your ideas, contribute or just to have a chat with us.
+
+## Features
+
+- Create blocks of code in which you can edit and run Python code
+- Move and resize blocks on an infinite 2D plane
+- Link blocks to highlight dependencies, Pyflow will then automatically run your blocks in the correct order
+- Convert your Jupyter notebooks to Pyflow graphs and vice versa
+
+## Installation
+
+Make sure you have Python 3 installed. You can download it from [here](https://www.python.org/downloads/)
+
+### Install PyFlow
+
+Using pip:
+
+```bash
+pip install byc-pyflow
+```
+
+### Run PyFlow
+
+```bash
+python -m pyflow
+```
+
+## Contributing
+
+If you are interested in contributing to the project, see [CONTRIBUTING.md](CONTRIBUTING.md).
+
+You can also join our [Discord](https://discord.gg/xZq8Tp4srd) to get in touch with us.
+
+## License
+
+See [LICENSE](LICENSE)
diff --git a/requirements.txt b/requirements.txt
index 51ee9172..28e0c2e1 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,5 +1,4 @@
pyqt5>=5.15.4
-QtPy>=1.9.0
qscintilla>=2.13.0
Ipython>=7.27.0
jupyter_client>=7.0.6
diff --git a/setup.py b/setup.py
new file mode 100644
index 00000000..fd717865
--- /dev/null
+++ b/setup.py
@@ -0,0 +1,45 @@
+# Pyflow an open-source tool for modular visual programing in python
+# Copyright (C) 2021-2022 Bycelium
+
+"""Setup for the python package build."""
+
+import pathlib
+from setuptools import setup, find_packages
+
+
+def get_version():
+ """Load version from file."""
+ version_file = open("VERSION")
+ return version_file.read().strip()
+
+
+def get_requirements():
+ """Load requirements from file."""
+ requirements_file = open("requirements.txt")
+ return requirements_file.readlines()
+
+
+HERE = pathlib.Path(__file__).parent # The directory containing this file
+README = (HERE / "pypi-readme.md").read_text()
+VERSION = get_version()
+REQUIREMENTS = get_requirements()
+
+setup(
+ name="byc-pyflow",
+ version=VERSION,
+ author="Bycelium",
+ author_email="mathis.federico@bycelium.com",
+ description="An open-source tool for modular visual programing in python",
+ long_description=README,
+ long_description_content_type="text/markdown",
+ url="https://github.com/Bycelium/PyFlow",
+ packages=find_packages(exclude=["*test*", "*docs*"]),
+ include_package_data=True,
+ install_requires=REQUIREMENTS,
+ classifiers=[
+ "Programming Language :: Python :: 3",
+ "License :: OSI Approved :: GNU General Public License v3 (GPLv3)",
+ "Operating System :: OS Independent",
+ ],
+ python_requires=">=3.7",
+)
diff --git a/tests/__init__.py b/tests/__init__.py
index fc97eed2..ba5e7a8f 100644
--- a/tests/__init__.py
+++ b/tests/__init__.py
@@ -1,4 +1,4 @@
# Pyflow an open-source tool for modular visual programing in python
-# Copyright (C) 2021 Mathïs FEDERICO
+# Copyright (C) 2021-2022 Bycelium
""" Tests for the pyflow package. """
diff --git a/tests/assets/example_graph1.ipyg b/tests/assets/example_graph1.ipyg
index c96688ed..c3743874 100644
--- a/tests/assets/example_graph1.ipyg
+++ b/tests/assets/example_graph1.ipyg
@@ -4,7 +4,7 @@
{
"id": 1523300599264,
"title": "test1",
- "block_type": "OCBCodeBlock",
+ "block_type": "CodeBlock",
"splitter_pos": [
292,
0
diff --git a/tests/assets/flow_test.ipyg b/tests/assets/flow_test.ipyg
index 0a20333b..710be340 100644
--- a/tests/assets/flow_test.ipyg
+++ b/tests/assets/flow_test.ipyg
@@ -4,7 +4,7 @@
{
"id": 2047380828024,
"title": "Test flow 5",
- "block_type": "OCBCodeBlock",
+ "block_type": "CodeBlock",
"splitter_pos": [
203,
0
@@ -58,7 +58,7 @@
{
"id": 2047509975832,
"title": "Test flow 1",
- "block_type": "OCBCodeBlock",
+ "block_type": "CodeBlock",
"splitter_pos": [
187,
0
@@ -112,7 +112,7 @@
{
"id": 2047510054808,
"title": "Test flow 2",
- "block_type": "OCBCodeBlock",
+ "block_type": "CodeBlock",
"splitter_pos": [
154,
0
@@ -166,7 +166,7 @@
{
"id": 2047510222024,
"title": "Test flow 3",
- "block_type": "OCBCodeBlock",
+ "block_type": "CodeBlock",
"splitter_pos": [
191,
0
@@ -220,7 +220,7 @@
{
"id": 2047510394344,
"title": "Test flow 4",
- "block_type": "OCBCodeBlock",
+ "block_type": "CodeBlock",
"splitter_pos": [
165,
0
@@ -274,7 +274,7 @@
{
"id": 2047510572632,
"title": "Test flow 6",
- "block_type": "OCBCodeBlock",
+ "block_type": "CodeBlock",
"splitter_pos": [
202,
0
@@ -328,7 +328,7 @@
{
"id": 2047511620200,
"title": "Test flow 7",
- "block_type": "OCBCodeBlock",
+ "block_type": "CodeBlock",
"splitter_pos": [
211,
0
@@ -382,7 +382,7 @@
{
"id": 2047511818888,
"title": "Test flow 8",
- "block_type": "OCBCodeBlock",
+ "block_type": "CodeBlock",
"splitter_pos": [
204,
0
@@ -436,7 +436,7 @@
{
"id": 2047514048984,
"title": "Markdown",
- "block_type": "OCBMarkdownBlock",
+ "block_type": "MarkdownBlock",
"splitter_pos": [
0,
130
@@ -460,7 +460,7 @@
{
"id": 2047515370984,
"title": "Test no connection 1",
- "block_type": "OCBCodeBlock",
+ "block_type": "CodeBlock",
"splitter_pos": [
199,
0
@@ -514,7 +514,7 @@
{
"id": 2047515582680,
"title": "Test input only 1",
- "block_type": "OCBCodeBlock",
+ "block_type": "CodeBlock",
"splitter_pos": [
250,
0
@@ -568,7 +568,7 @@
{
"id": 2047516568312,
"title": "Test input only 2",
- "block_type": "OCBCodeBlock",
+ "block_type": "CodeBlock",
"splitter_pos": [
236,
0
@@ -622,7 +622,7 @@
{
"id": 2047517406264,
"title": "Test output only 1",
- "block_type": "OCBCodeBlock",
+ "block_type": "CodeBlock",
"splitter_pos": [
209,
0
@@ -676,7 +676,7 @@
{
"id": 2047520151448,
"title": "Test output only 2",
- "block_type": "OCBCodeBlock",
+ "block_type": "CodeBlock",
"splitter_pos": [
202,
0
@@ -730,7 +730,7 @@
{
"id": 2047565442936,
"title": "Markdown",
- "block_type": "OCBMarkdownBlock",
+ "block_type": "MarkdownBlock",
"splitter_pos": [
0,
131
diff --git a/tests/integration/__init__.py b/tests/integration/__init__.py
index 419e85cc..3e50fa28 100644
--- a/tests/integration/__init__.py
+++ b/tests/integration/__init__.py
@@ -1,8 +1,8 @@
# Pyflow an open-source tool for modular visual programing in python
-# Copyright (C) 2021 Mathïs FEDERICO
+# Copyright (C) 2021-2022 Bycelium
"""
-Integration tests for the OCB package.
+Integration tests for the package.
We use xvfb to perform the tests without opening any windows.
We use pyautogui to move the mouse and interact with the application.
diff --git a/tests/integration/blocks/__init__.py b/tests/integration/blocks/__init__.py
index e0d043db..5afedbbd 100644
--- a/tests/integration/blocks/__init__.py
+++ b/tests/integration/blocks/__init__.py
@@ -1,5 +1,5 @@
# Pyflow an open-source tool for modular visual programing in python
-# Copyright (C) 2021 Mathïs FEDERICO
+# Copyright (C) 2021-2022 Bycelium
"""
Integration tests for blocks behavior.
diff --git a/tests/integration/blocks/test_block.py b/tests/integration/blocks/test_block.py
index 39ab970d..d6647201 100644
--- a/tests/integration/blocks/test_block.py
+++ b/tests/integration/blocks/test_block.py
@@ -1,8 +1,8 @@
# Pyflow an open-source tool for modular visual programing in python
-# Copyright (C) 2021 Mathïs FEDERICO
+# Copyright (C) 2021-2022 Bycelium
"""
-Integration tests for the OCBBlocks.
+Integration tests for the Blocks.
"""
import pytest
@@ -11,7 +11,7 @@
from PyQt5.QtCore import QPointF
-from pyflow.blocks.block import OCBBlock
+from pyflow.blocks.block import Block
from tests.integration.utils import apply_function_inapp, CheckingQueue, start_app
@@ -21,18 +21,18 @@ class TestBlocks:
def setup(self):
"""Setup reused variables."""
start_app(self)
- self.block = OCBBlock(title="Testing block")
+ self.block = Block(title="Testing block")
def test_create_blocks(self, qtbot: QtBot):
"""can be added to the scene."""
- self.ocb_widget.scene.addItem(self.block)
+ self._widget.scene.addItem(self.block)
def test_move_blocks(self, qtbot: QtBot):
"""can be dragged around with the mouse."""
- self.ocb_widget.scene.addItem(self.block)
- self.ocb_widget.view.horizontalScrollBar().setValue(self.block.x())
- self.ocb_widget.view.verticalScrollBar().setValue(
- self.block.y() - self.ocb_widget.view.height() + self.block.height
+ self._widget.scene.addItem(self.block)
+ self._widget.view.horizontalScrollBar().setValue(self.block.x())
+ self._widget.view.verticalScrollBar().setValue(
+ self.block.y() - self._widget.view.height() + self.block.height
)
def testing_drag(msgQueue: CheckingQueue):
@@ -47,8 +47,8 @@ def testing_drag(msgQueue: CheckingQueue):
)
pos_block.setY(pos_block.y() + self.block.title_widget.height() / 2)
- pos_block = self.ocb_widget.view.mapFromScene(pos_block)
- pos_block = self.ocb_widget.view.mapToGlobal(pos_block)
+ pos_block = self._widget.view.mapFromScene(pos_block)
+ pos_block = self._widget.view.mapToGlobal(pos_block)
pyautogui.moveTo(pos_block.x(), pos_block.y())
pyautogui.mouseDown(button="left")
@@ -64,8 +64,8 @@ def testing_drag(msgQueue: CheckingQueue):
move_amount = [self.block.pos().x(), self.block.pos().y()]
# rectify because the scene can be zoomed :
- move_amount[0] = move_amount[0] * self.ocb_widget.view.zoom
- move_amount[1] = move_amount[1] * self.ocb_widget.view.zoom
+ move_amount[0] = move_amount[0] * self._widget.view.zoom
+ move_amount[1] = move_amount[1] * self._widget.view.zoom
msgQueue.check_equal(
move_amount, expected_move_amount, "Block moved by the correct amound"
diff --git a/tests/integration/blocks/test_codeblock.py b/tests/integration/blocks/test_codeblock.py
index 9418caf4..5ddd26b8 100644
--- a/tests/integration/blocks/test_codeblock.py
+++ b/tests/integration/blocks/test_codeblock.py
@@ -1,8 +1,8 @@
# Pyflow an open-source tool for modular visual programing in python
-# Copyright (C) 2021 Mathïs FEDERICO
+# Copyright (C) 2021-2022 Bycelium
"""
-Integration tests for the OCBCodeBlocks.
+Integration tests for the CodeBlocks.
"""
import time
@@ -12,7 +12,7 @@
from PyQt5.QtCore import QPointF
-from pyflow.blocks.codeblock import OCBCodeBlock
+from pyflow.blocks.codeblock import CodeBlock
from tests.integration.utils import apply_function_inapp, CheckingQueue, start_app
@@ -31,8 +31,8 @@ def test_run_python(self):
SOURCE_TEST = f"""print({EXPRESSION})"""
expected_result = str(3 + 5 * 2)
- test_block = OCBCodeBlock(title="CodeBlock test", source=SOURCE_TEST)
- self.ocb_widget.scene.addItem(test_block)
+ test_block = CodeBlock(title="CodeBlock test", source=SOURCE_TEST)
+ self._widget.scene.addItem(test_block)
def testing_run(msgQueue: CheckingQueue):
@@ -43,8 +43,8 @@ def testing_run(msgQueue: CheckingQueue):
pos_run_button.x() + test_block.run_button.width() / 2,
pos_run_button.y() + test_block.run_button.height() / 2,
)
- pos_run_button = self.ocb_widget.view.mapFromScene(pos_run_button)
- pos_run_button = self.ocb_widget.view.mapToGlobal(pos_run_button)
+ pos_run_button = self._widget.view.mapFromScene(pos_run_button)
+ pos_run_button = self._widget.view.mapToGlobal(pos_run_button)
# Run the block by pressung the run button
pyautogui.moveTo(pos_run_button.x(), pos_run_button.y())
@@ -52,7 +52,7 @@ def testing_run(msgQueue: CheckingQueue):
pyautogui.mouseUp(button="left")
time.sleep((test_block.transmitting_duration / 1000) + 0.2)
- while test_block.run_color != 0:
+ while test_block.run_state != 0:
time.sleep(0.1)
msgQueue.check_equal(test_block.stdout.strip(), expected_result)
@@ -61,15 +61,15 @@ def testing_run(msgQueue: CheckingQueue):
apply_function_inapp(self.window, testing_run)
def test_run_block_with_path(self):
- """runs blocks with the correct working directory for the kernel"""
+ """runs blocks with the correct working directory for the kernel."""
file_example_path = "./tests/assets/example_graph1.ipyg"
asset_path = "./tests/assets/data.txt"
- self.ocb_widget.scene.load(os.path.abspath(file_example_path))
+ self._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: CodeBlock = None
+ for item in self._widget.scene.items():
+ if isinstance(item, CodeBlock) and item.title == "test1":
block_of_test = item
break
msgQueue.check_equal(
@@ -83,7 +83,7 @@ def run_block():
msgQueue.run_lambda(run_block)
time.sleep(0.1) # wait for the lambda to complete.
- while block_of_test.run_color != 0:
+ while block_of_test.run_state != 0:
time.sleep(0.1) # wait for the execution to finish.
time.sleep(0.1)
diff --git a/tests/integration/blocks/test_flow.py b/tests/integration/blocks/test_flow.py
index ac864559..672c5311 100644
--- a/tests/integration/blocks/test_flow.py
+++ b/tests/integration/blocks/test_flow.py
@@ -1,5 +1,5 @@
# Pyflow an open-source tool for modular visual programing in python
-# Copyright (C) 2021 Mathïs FEDERICO
+# Copyright (C) 2021-2022 Bycelium
"""
Integration tests for the execution flow.
@@ -8,18 +8,18 @@
import pytest
import time
-from pyflow.blocks.codeblock import OCBCodeBlock
+from pyflow.blocks.codeblock import CodeBlock
from tests.integration.utils import apply_function_inapp, CheckingQueue, start_app
-class TestCodeBlocks:
+class TestCodeBlocksFlow:
@pytest.fixture(autouse=True)
def setup(self):
"""Setup reused variables."""
start_app(self)
- self.ocb_widget.scene.load("tests/assets/flow_test.ipyg")
+ self._widget.scene.load("tests/assets/flow_test.ipyg")
self.titles = [
"Test flow 5",
@@ -29,26 +29,26 @@ def setup(self):
"Test output only 1",
]
self.blocks_to_run = [None] * 5
- for item in self.ocb_widget.scene.items():
- if isinstance(item, OCBCodeBlock):
+ for item in self._widget.scene.items():
+ if isinstance(item, CodeBlock):
if item.title in self.titles:
self.blocks_to_run[self.titles.index(item.title)] = item
def test_duplicated_run(self):
- """Don't run a block twice when the execution flows"""
+ """run exactly one time pressing run right."""
for b in self.blocks_to_run:
b.stdout = ""
def testing_no_duplicates(msgQueue: CheckingQueue):
- block_to_run: OCBCodeBlock = self.blocks_to_run[0]
+ block_to_run: CodeBlock = self.blocks_to_run[0]
def run_block():
block_to_run.run_right()
msgQueue.run_lambda(run_block)
time.sleep((block_to_run.transmitting_duration / 1000) + 0.2)
- while block_to_run.run_color != 0:
+ while block_to_run.run_state != 0:
time.sleep(0.1)
# 6 and not 6\n6
@@ -58,22 +58,22 @@ def run_block():
apply_function_inapp(self.window, testing_no_duplicates)
def test_flow_left(self):
- """Correct flow when pressing left run"""
+ """run its dependencies 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[0]
- block_to_not_run: OCBCodeBlock = self.blocks_to_run[1]
+ block_to_run: CodeBlock = self.blocks_to_run[0]
+ block_to_not_run: CodeBlock = self.blocks_to_run[1]
def run_block():
block_to_run.run_left()
msgQueue.run_lambda(run_block)
time.sleep((block_to_run.transmitting_duration / 1000) + 0.2)
- while block_to_run.run_color != 0:
+ while block_to_run.run_state != 0:
time.sleep(0.1)
msgQueue.check_equal(block_to_run.stdout.strip(), "6")
@@ -83,11 +83,11 @@ def run_block():
apply_function_inapp(self.window, testing_run)
def test_no_connection_left(self):
- """run block only when no previous connection."""
+ """run itself only when has no dependecy and pressing left run."""
def testing_run(msgQueue: CheckingQueue):
- block_to_run: OCBCodeBlock = self.blocks_to_run[
+ block_to_run: CodeBlock = self.blocks_to_run[
self.titles.index("Test no connection 1")
]
@@ -98,7 +98,7 @@ def run_block():
msgQueue.run_lambda(run_block)
time.sleep((block_to_run.transmitting_duration / 1000) + 0.2)
- while block_to_run.run_color != 0:
+ while block_to_run.run_state != 0:
time.sleep(0.1)
msgQueue.check_equal(block_to_run.stdout.strip(), "1")
@@ -107,11 +107,11 @@ def run_block():
apply_function_inapp(self.window, testing_run)
def test_no_connection_right(self):
- """run block only when no next connection."""
+ """run itself only when is not a dependecy and pressing right run."""
def testing_run(msgQueue: CheckingQueue):
- block_to_run: OCBCodeBlock = self.blocks_to_run[
+ block_to_run: CodeBlock = self.blocks_to_run[
self.titles.index("Test no connection 1")
]
@@ -120,7 +120,7 @@ def run_block():
msgQueue.run_lambda(run_block)
time.sleep((block_to_run.transmitting_duration / 1000) + 0.2)
- while block_to_run.run_color != 0:
+ while block_to_run.run_state != 0:
time.sleep(0.1)
# Just check that it doesn't crash
diff --git a/tests/integration/test_window.py b/tests/integration/test_window.py
index 62cf5fd2..ee668aa1 100644
--- a/tests/integration/test_window.py
+++ b/tests/integration/test_window.py
@@ -1,31 +1,31 @@
# Pyflow an open-source tool for modular visual programing in python
-# Copyright (C) 2021 Mathïs FEDERICO
+# Copyright (C) 2021-2022 Bycelium
"""
-Integration tests for the OCBWindow.
+Integration tests for the Window.
"""
import os
import pytest
from pytest_mock import MockerFixture
-from pyflow.graphics.window import OCBWindow
+from pyflow.graphics.window import Window
class TestWindow:
@pytest.fixture(autouse=True)
def setup(self, mocker: MockerFixture):
"""Setup reused variables."""
- self.window = OCBWindow()
+ self.window = Window()
def test_open_file(self, qtbot):
- """loads files"""
- wnd = OCBWindow()
+ """loads files."""
+ wnd = Window()
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"""
+ """closes."""
self.window.close()
diff --git a/tests/integration/utils.py b/tests/integration/utils.py
index d34d8b9d..948b7b02 100644
--- a/tests/integration/utils.py
+++ b/tests/integration/utils.py
@@ -1,5 +1,5 @@
# Pyflow an open-source tool for modular visual programing in python
-# Copyright (C) 2021 Mathïs FEDERICO
+# Copyright (C) 2021-2022 Bycelium
"""
Utilities functions for integration testing.
@@ -8,17 +8,21 @@
from typing import Callable
import os
-import asyncio
+import warnings
import threading
import time
from queue import Queue
-from qtpy.QtWidgets import QApplication
import pytest_check as check
-import warnings
-from pyflow.graphics.widget import OCBWidget
+import asyncio
-from pyflow.graphics.window import OCBWindow
+from PyQt5.QtWidgets import QApplication
+
+from pyflow.graphics.widget import Widget
+from pyflow.graphics.window import Window
+
+if os.name == "nt": # If on windows
+ asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
STOP_MSG = "stop"
CHECK_MSG = "check"
@@ -37,45 +41,41 @@ def stop(self):
class ExceptionForwardingThread(threading.Thread):
- """A Thread class that forwards the exceptions to the calling thread"""
+ """A Thread class that forwards the exceptions to the calling thread."""
def __init__(self, *args, **kwargs):
- """Create an exception forwarding thread"""
+ """Create an exception forwarding thread."""
super().__init__(*args, **kwargs)
- self.e = None
+ self.exeption = None
def run(self):
- """Code ran in another thread"""
+ """Code ran in another thread."""
try:
super().run()
except Exception as e:
- self.e = e
+ self.exeption = e
def join(self):
- """Used to sync the thread with the caller"""
+ """Used to sync the thread with the caller."""
super().join()
- print("except: ", self.e)
- if self.e != None:
- raise self.e
+ print("except: ", self.exeption)
+ if self.exeption is not None:
+ raise self.exeption
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)
+ """Create a new app for testing purpose."""
+ obj.window = Window()
+ obj._widget = Widget()
+ obj.subwindow = obj.window.mdiArea.addSubWindow(obj._widget)
obj.subwindow.show()
-def apply_function_inapp(window: OCBWindow, run_func: Callable):
-
- if os.name == "nt": # If on windows
- asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
-
+def apply_function_inapp(window: Window, run_func: Callable):
QApplication.processEvents()
msgQueue = CheckingQueue()
- t = ExceptionForwardingThread(target=run_func, args=(msgQueue,))
- t.start()
+ thread = ExceptionForwardingThread(target=run_func, args=(msgQueue,))
+ thread.start()
stop = False
deadCounter = 0
@@ -92,7 +92,7 @@ def apply_function_inapp(window: OCBWindow, run_func: Callable):
elif msg[0] == RUN_MSG:
msg[1](*msg[2], **msg[3])
- if not t.is_alive() and not stop:
+ if not thread.is_alive() and not stop:
deadCounter += 1
if deadCounter >= 3:
# Test failed, close was not called
@@ -100,4 +100,4 @@ def apply_function_inapp(window: OCBWindow, run_func: Callable):
"Warning: you need to call CheckingQueue.stop() at the end of your test !"
)
break
- t.join()
+ thread.join()
diff --git a/tests/unit/__init__.py b/tests/unit/__init__.py
index 2e33fcf8..f99ab9b4 100644
--- a/tests/unit/__init__.py
+++ b/tests/unit/__init__.py
@@ -1,4 +1,4 @@
# Pyflow an open-source tool for modular visual programing in python
-# Copyright (C) 2021 Mathïs FEDERICO
+# Copyright (C) 2021-2022 Bycelium
""" Unit tests for the pyflow package. """
diff --git a/tests/unit/scene/__init__.py b/tests/unit/scene/__init__.py
index 47614f0e..2352425e 100644
--- a/tests/unit/scene/__init__.py
+++ b/tests/unit/scene/__init__.py
@@ -1,4 +1,4 @@
# Pyflow an open-source tool for modular visual programing in python
-# Copyright (C) 2021 Mathïs FEDERICO
+# Copyright (C) 2021-2022 Bycelium
""" Unit tests for the pyflow scene module. """
diff --git a/tests/unit/scene/test_clipboard.py b/tests/unit/scene/test_clipboard.py
index cdf77d64..4321be3b 100644
--- a/tests/unit/scene/test_clipboard.py
+++ b/tests/unit/scene/test_clipboard.py
@@ -1,5 +1,5 @@
# Pyflow an open-source tool for modular visual programing in python
-# Copyright (C) 2021 Mathïs FEDERICO
+# Copyright (C) 2021-2022 Bycelium
""" Unit tests for the pyflow history module. """
diff --git a/tests/unit/scene/test_history.py b/tests/unit/scene/test_history.py
index bd2dd8a6..907e2fc2 100644
--- a/tests/unit/scene/test_history.py
+++ b/tests/unit/scene/test_history.py
@@ -1,5 +1,5 @@
# Pyflow an open-source tool for modular visual programing in python
-# Copyright (C) 2021 Mathïs FEDERICO
+# Copyright (C) 2021-2022 Bycelium
""" Unit tests for the pyflow history module. """
diff --git a/tests/unit/scene/test_ipynb_conversion.py b/tests/unit/scene/test_ipynb_conversion.py
index 9376644b..0b3fa71c 100644
--- a/tests/unit/scene/test_ipynb_conversion.py
+++ b/tests/unit/scene/test_ipynb_conversion.py
@@ -1,3 +1,6 @@
+# Pyflow an open-source tool for modular visual programing in python
+# Copyright (C) 2021-2022 Bycelium
+
"""Unit tests for the conversion from and to ipynb."""
from typing import OrderedDict
diff --git a/utils.py b/utils.py
index 59141d56..cf48f705 100644
--- a/utils.py
+++ b/utils.py
@@ -1,7 +1,7 @@
# Pyflow an open-source tool for modular visual programing in python
-# Copyright (C) 2021 Mathïs FEDERICO
+# Copyright (C) 2021-2022 Bycelium
-""" Module for badges colors """
+""" Module for badges colors."""
from colorsys import hsv_to_rgb