Skip to content

Commit 7abdf5a

Browse files
committedSep 23, 2024
adding pre-commit with static type checking
1 parent 4155d9b commit 7abdf5a

26 files changed

+509
-277
lines changed
 

‎.gitignore

+1-1
Original file line numberDiff line numberDiff line change
@@ -106,4 +106,4 @@ venv.bak/
106106
temp
107107

108108
#vscode settings
109-
settings.json
109+
settings.json

‎.pre-commit-config.yaml

+40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
# See https://pre-commit.com for more information
2+
# See https://pre-commit.com/hooks.html for more hooks
3+
repos:
4+
- repo: https://github.com/pre-commit/pre-commit-hooks
5+
rev: v4.6.0 # Use the ref you want to point at
6+
hooks:
7+
- id: trailing-whitespace
8+
- id: end-of-file-fixer
9+
- id: check-yaml
10+
- id: check-added-large-files
11+
12+
- repo: https://github.com/psf/black
13+
rev: 24.8.0
14+
hooks:
15+
- id: black
16+
args: [--line-length=120]
17+
18+
- repo: https://github.com/pre-commit/mirrors-mypy
19+
rev: v1.11.2
20+
hooks:
21+
- id: mypy
22+
args: [--strict, --ignore-missing-imports]
23+
24+
- repo: https://github.com/pycqa/flake8
25+
rev: 7.1.1
26+
hooks:
27+
- id: flake8
28+
additional_dependencies: [flake8-typing-imports==1.12.0]
29+
args: [--max-line-length=120]
30+
31+
- repo: https://github.com/asottile/reorder_python_imports
32+
rev: v3.13.0
33+
hooks:
34+
- id: reorder-python-imports
35+
args: [--py37-plus, --add-import, 'from __future__ import annotations']
36+
37+
- repo: https://github.com/asottile/setup-cfg-fmt
38+
rev: v2.5.0
39+
hooks:
40+
- id: setup-cfg-fmt

‎Example_Project/Add_node.py

+19-3
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,16 @@
1+
from __future__ import annotations
2+
3+
from typing import Tuple
4+
15
from node_editor.node import Node
26

37

48
class Add_Node(Node):
5-
def __init__(self):
9+
def __init__(self) -> None:
610
super().__init__()
711

8-
self.title_text = "Add"
9-
self.type_text = "Logic Nodes"
12+
self.title_text: str = "Add"
13+
self.type_text: str = "Logic Nodes"
1014
self.set_color(title_color=(0, 128, 0))
1115

1216
self.add_pin(name="Ex In", is_output=False, execution=True)
@@ -16,3 +20,15 @@ def __init__(self):
1620
self.add_pin(name="input B", is_output=False)
1721
self.add_pin(name="output", is_output=True)
1822
self.build()
23+
24+
def set_color(self, title_color: Tuple[int, int, int]) -> None:
25+
# Assuming set_color is defined in the parent class
26+
super().set_color(title_color=title_color)
27+
28+
def add_pin(self, name: str, is_output: bool, execution: bool = False) -> None:
29+
# Assuming add_pin is defined in the parent class
30+
super().add_pin(name=name, is_output=is_output, execution=execution)
31+
32+
def build(self) -> None:
33+
# Assuming build is defined in the parent class
34+
super().build()

‎Example_Project/Print_node.py

+19-3
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,31 @@
1+
from __future__ import annotations
2+
3+
from typing import Tuple
4+
15
from node_editor.node import Node
26

37

48
class Print_Node(Node):
5-
def __init__(self):
9+
def __init__(self) -> None:
610
super().__init__()
711

8-
self.title_text = "Print"
9-
self.type_text = "Debug Nodes"
12+
self.title_text: str = "Print"
13+
self.type_text: str = "Debug Nodes"
1014
self.set_color(title_color=(160, 32, 240))
1115

1216
self.add_pin(name="Ex In", is_output=False, execution=True)
1317

1418
self.add_pin(name="input", is_output=False)
1519
self.build()
20+
21+
def set_color(self, title_color: Tuple[int, int, int]) -> None:
22+
# Assuming set_color is defined in the parent class
23+
super().set_color(title_color=title_color)
24+
25+
def add_pin(self, name: str, is_output: bool, execution: bool = False) -> None:
26+
# Assuming add_pin is defined in the parent class
27+
super().add_pin(name=name, is_output=is_output, execution=execution)
28+
29+
def build(self) -> None:
30+
# Assuming build is defined in the parent class
31+
super().build()

‎Example_Project/Scaler_node.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1+
from __future__ import annotations
2+
13
from PySide6 import QtWidgets
24

3-
from node_editor.node import Node
45
from Example_Project.common_widgets import FloatLineEdit
6+
from node_editor.node import Node
57

68

79
class Scaler_Node(Node):

‎Example_Project/__init__.py

Whitespace-only changes.

‎Example_Project/common_widgets.py

+17-10
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,35 @@
1-
from PySide6 import QtWidgets
1+
from __future__ import annotations
2+
3+
from typing import Optional
4+
from typing import Tuple
5+
26
from PySide6 import QtGui
7+
from PySide6 import QtWidgets
38
from PySide6.QtCore import Qt
49

5-
class FloatLineEdit(QtWidgets.QLineEdit):
6-
def __init__(self, parent=None):
10+
11+
class FloatLineEdit(QtWidgets.QLineEdit): # type: ignore
12+
def __init__(self, parent: Optional[QtWidgets.QWidget] = None) -> None:
713
super().__init__(parent)
814
self.setValidator(FloatValidator())
915

10-
def keyPressEvent(self, event):
16+
def keyPressEvent(self, event: QtGui.QKeyEvent) -> None:
1117
if event.key() == Qt.Key_Space:
1218
event.ignore()
1319
else:
1420
super().keyPressEvent(event)
1521

16-
class FloatValidator(QtGui.QDoubleValidator):
17-
def __init__(self, parent=None):
22+
23+
class FloatValidator(QtGui.QDoubleValidator): # type: ignore
24+
def __init__(self, parent: Optional[QtGui.QObject] = None) -> None:
1825
super().__init__(parent)
1926

20-
def validate(self, input_str, pos):
27+
def validate(self, input_str: str, pos: int) -> Tuple[QtGui.QValidator.State, str, int]:
2128
state, num, pos = super().validate(input_str, pos)
2229
if state == QtGui.QValidator.Acceptable:
2330
return QtGui.QValidator.Acceptable, num, pos
24-
if str(num).count('.') > 1:
31+
if str(num).count(".") > 1:
2532
return QtGui.QValidator.Invalid, num, pos
26-
if input_str[pos-1] == '.':
33+
if input_str[pos - 1] == ".":
2734
return QtGui.QValidator.Acceptable, num, pos
28-
return QtGui.QValidator.Invalid, num, pos
35+
return QtGui.QValidator.Invalid, num, pos

‎Example_Project/test.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -63,4 +63,4 @@
6363
"end_pin": "Ex In"
6464
}
6565
]
66-
}
66+
}

‎README.md

+2-3
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ The tool is designed to allow you to write Python code in individual files per c
66

77
My goal with this project is to provide a new and innovative way of organizing and working with Python code. While the tool is still in the development phase, I am constantly working to improve its functionality and features.
88

9-
Visual scripting using nodes does have some benefits and drawbacks and it’s up to the end developer to decide when such a system is beneficial or not.
9+
Visual scripting using nodes does have some benefits and drawbacks and it’s up to the end developer to decide when such a system is beneficial or not.
1010

1111
![nodes](https://github.com/bhowiebkr/simple-node-editor/blob/master/images/node_editor2.jpg)
1212

@@ -16,7 +16,7 @@ Use it for:
1616
- enabling non-programmers a simple system to assemble blocks of logic
1717
- networks that require a high level of feedback throughout that network and not just the end result. Example shader building, sound synthesizing, machine learning, robotics and sensors. Each node can have a custom visual feedback such as images, graphs, sound timelines, spreadsheets etc.
1818
- prototyping logic.
19-
- Generator scripts. Taking an input or building up a result that gets saved for other uses. Example textures, images, sound, ML training data.
19+
- Generator scripts. Taking an input or building up a result that gets saved for other uses. Example textures, images, sound, ML training data.
2020

2121
Don’t use it for
2222
- Anything complex. 40 nodes or less. This is because the user not only needs to think of how nodes are logically connected, but also the visual composure of nodes in the graph. It’s always best to refactor code when a graph gets too complex to make sense of.
@@ -27,4 +27,3 @@ For minimal GUI code for creating a node network see [GUI-nodes-only](https://gi
2727

2828

2929
[![Video](http://img.youtube.com/vi/DOsFJ8lm9dU/0.jpg)](http://www.youtube.com/watch?v=DOsFJ8lm9dU)
30-

‎launch.bat

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
@echo off
2+
3+
:: Activate the virtual environment
4+
call venv\Scripts\activate
5+
6+
:: Start the three Python programs in separate command windows
7+
start cmd /k python .\main.py

‎main.py

+34-27
Original file line numberDiff line numberDiff line change
@@ -9,28 +9,39 @@
99
Author: Bryan Howard
1010
Repo: https://github.com/bhowiebkr/simple-node-editor
1111
"""
12+
from __future__ import annotations
1213

13-
import logging
14-
from pathlib import Path
1514
import importlib
1615
import inspect
16+
import logging
17+
import sys
18+
from pathlib import Path
19+
from typing import Any
20+
from typing import Dict
21+
from typing import Optional
1722

18-
from PySide6 import QtCore, QtGui, QtWidgets
23+
import qdarktheme
24+
from PySide6 import QtCore
25+
from PySide6 import QtGui
26+
from PySide6 import QtWidgets
27+
from PySide6.QtCore import QByteArray # Or from PySide2.QtCore import QByteArray
1928

2029
from node_editor.gui.node_list import NodeList
2130
from node_editor.gui.node_widget import NodeWidget
2231

2332
logging.basicConfig(level=logging.DEBUG)
2433

2534

26-
class NodeEditor(QtWidgets.QMainWindow):
35+
class NodeEditor(QtWidgets.QMainWindow): # type: ignore
2736
OnProjectPathUpdate = QtCore.Signal(Path)
2837

29-
def __init__(self, parent=None):
38+
def __init__(self, parent: Optional[QtWidgets.QWidget] = None) -> None:
3039
super().__init__(parent)
31-
self.settings = None
32-
self.project_path = None
33-
self.imports = None # we will store the project import node types here for now.
40+
self.settings: Optional[QtCore.QSettings] = None
41+
self.project_path: Optional[Path] = None
42+
self.imports: Optional[Dict[str, Dict[str, Any]]] = (
43+
None # we will store the project import node types here for now.
44+
)
3445

3546
icon = QtGui.QIcon("resources\\app.ico")
3647
self.setWindowIcon(icon)
@@ -59,10 +70,10 @@ def __init__(self, parent=None):
5970
left_layout.setContentsMargins(0, 0, 0, 0)
6071

6172
# Widgets
62-
self.node_list = NodeList(self)
73+
self.node_list: NodeList = NodeList(self)
6374
left_widget = QtWidgets.QWidget()
64-
self.splitter = QtWidgets.QSplitter()
65-
self.node_widget = NodeWidget(self)
75+
self.splitter: QtWidgets.QSplitter = QtWidgets.QSplitter()
76+
self.node_widget: NodeWidget = NodeWidget(self)
6677

6778
# Add Widgets to layouts
6879
self.splitter.addWidget(left_widget)
@@ -72,25 +83,25 @@ def __init__(self, parent=None):
7283
main_layout.addWidget(self.splitter)
7384

7485
# Load the example project
75-
example_project_path = (Path(__file__).parent.resolve() / 'Example_project')
86+
example_project_path = Path(__file__).parent.resolve() / "Example_project"
7687
self.load_project(example_project_path)
7788

7889
# Restore GUI from last state
7990
if settings.contains("geometry"):
80-
self.restoreGeometry(settings.value("geometry"))
91+
self.restoreGeometry(QByteArray(settings.value("geometry")))
8192

8293
s = settings.value("splitterSize")
8394
self.splitter.restoreState(s)
8495

85-
def save_project(self):
96+
def save_project(self) -> None:
8697
file_dialog = QtWidgets.QFileDialog()
8798
file_dialog.setAcceptMode(QtWidgets.QFileDialog.AcceptSave)
8899
file_dialog.setDefaultSuffix("json")
89100
file_dialog.setNameFilter("JSON files (*.json)")
90101
file_path, _ = file_dialog.getSaveFileName()
91102
self.node_widget.save_project(file_path)
92103

93-
def load_project(self, project_path=None):
104+
def load_project(self, project_path: Optional[Path] = None) -> None:
94105
if not project_path:
95106
return
96107

@@ -101,36 +112,35 @@ def load_project(self, project_path=None):
101112
self.imports = {}
102113

103114
for file in project_path.glob("*.py"):
104-
105-
if not file.stem.endswith('_node'):
106-
print('file:', file.stem)
115+
if not file.stem.endswith("_node"):
116+
print("file:", file.stem)
107117
continue
108118
spec = importlib.util.spec_from_file_location(file.stem, file)
109119
module = importlib.util.module_from_spec(spec)
110120
spec.loader.exec_module(module)
111121

112122
for name, obj in inspect.getmembers(module):
113-
if not name.endswith('_Node'):
123+
if not name.endswith("_Node"):
114124
continue
115125
if inspect.isclass(obj):
116126
self.imports[obj.__name__] = {"class": obj, "module": module}
117-
#break
127+
# break
118128

119129
self.node_list.update_project(self.imports)
120130

121131
# work on just the first json file. add the ablitity to work on multiple json files later
122132
for json_path in project_path.glob("*.json"):
123-
self.node_widget.load_scene(json_path, self.imports)
133+
self.node_widget.load_scene(str(json_path), self.imports)
124134
break
125135

126-
def get_project_path(self):
136+
def get_project_path(self) -> None:
127137
project_path = QtWidgets.QFileDialog.getExistingDirectory(None, "Select Project Folder", "")
128138
if not project_path:
129139
return
130140

131-
self.load_project(project_path)
141+
self.load_project(Path(project_path))
132142

133-
def closeEvent(self, event):
143+
def closeEvent(self, event: QtGui.QCloseEvent) -> None:
134144
"""
135145
Handles the close event by saving the GUI state and closing the application.
136146
@@ -151,9 +161,6 @@ def closeEvent(self, event):
151161

152162

153163
if __name__ == "__main__":
154-
import sys
155-
156-
import qdarktheme
157164

158165
app = QtWidgets.QApplication(sys.argv)
159166
app.setWindowIcon(QtGui.QIcon("resources\\app.ico"))

‎mypy.ini

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
[mypy]
2+
python_version = 3.8
3+
ignore_missing_imports = True
4+
strict = True

‎node_editor/__init__.py

Whitespace-only changes.

‎node_editor/common.py

+2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
from __future__ import annotations
2+
13
from enum import Enum
24

35

0 commit comments

Comments
 (0)
Please sign in to comment.