Skip to content

Commit

Permalink
type checking for Ryven
Browse files Browse the repository at this point in the history
  • Loading branch information
leon-thomm committed May 19, 2024
1 parent a40ab40 commit 1537aee
Show file tree
Hide file tree
Showing 27 changed files with 206 additions and 131 deletions.
21 changes: 21 additions & 0 deletions mypy.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# mypy configuration, type-checking both the Ryven editor, and the
# ryvencore-qt library. ryvencore must be installed for this to work.
# Simply run `mypy` in the Ryven root directory to check the code.

[mypy]
warn_return_any = True
warn_unused_configs = True
warn_unused_ignores = True
files = ryven-editor/ryven, ryvencore-qt/ryvencore_qt

[mypy-ryven.*]
check_untyped_defs = False

[mypy-ryven.example_nodes.*]
ignore_errors = True

[mypy-ryven.main.packages.built_in.*]
ignore_errors = True

[mypy-ryven.gui.uic.*]
ignore_errors = True
26 changes: 15 additions & 11 deletions ryven-editor/ryven/example_nodes/std/special_nodes.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from typing import Dict, Set
import code
from contextlib import redirect_stdout, redirect_stderr
from packaging.version import Version
Expand Down Expand Up @@ -155,8 +156,8 @@ class Log_Node(DualNodeBase):
]
GUI = guis.LogNodeGui

logs = {} # {int: Logger}
in_use = set() # make sure we don't reuse numbers on copy & paste
logs: Dict[int, logging.Logger] = {} # {int: Logger}
in_use: Set[int] = set() # make sure we don't reuse numbers on copy & paste

def __init__(self, params):
super().__init__(params, active=True)
Expand Down Expand Up @@ -202,8 +203,11 @@ def set_state(self, data: dict, version):

# the logging addon will have re-created the logger
# for us already
self.logs[self.number] = \
self.logs_ext().new_logger(self, 'Log Node')
l = self.logs_ext().new_logger(self, 'Log Node')
if l is None:
print(f'WARNING: logger {self.number} for Log Node {self} already exists')
else:
self.logs[self.number] = l


class Clock_Node(NodeBase):
Expand Down Expand Up @@ -439,7 +443,7 @@ def _hist_updated(self):
def process_input(self, cmds: str):
m = self.COMMANDS.get(cmds)
if m is not None:
m()
m(self)
else:
for l in cmds.splitlines():
self.write(l) # print input
Expand All @@ -452,7 +456,7 @@ def run_src():
self.buffer.clear()

if self.session.gui:
with redirect_stdout(self), redirect_stderr(self):
with redirect_stdout(self), redirect_stderr(self): # type: ignore
run_src()
else:
run_src()
Expand Down Expand Up @@ -519,7 +523,7 @@ class LinkIN_Node(NodeBase):
GUI = guis.LinkIN_NodeGui

# instances registration
INSTANCES = {} # {UUID: node}
INSTANCES: Dict[str, Node] = {}

def __init__(self, params):
super().__init__(params)
Expand Down Expand Up @@ -575,12 +579,12 @@ class LinkOUT_Node(NodeBase):
"""The complement to the link IN node"""

title = 'link OUT'
init_inputs = [] # no inputs
init_outputs = [] # will be synchronized with linked IN node
init_inputs: List[NodeInputType] = [] # no inputs
init_outputs: List[NodeOutputType] = [] # will be synchronized with linked IN node
GUI = guis.LinkOUT_NodeGui

INSTANCES = []
PENDING_LINK_BUILDS = {}
INSTANCES: List['LinkOUT_Node'] = []
PENDING_LINK_BUILDS: Dict['LinkOUT_Node', str] = {}
# because a link OUT node might get initialized BEFORE it's corresponding
# link IN, it then stores itself together with the ID of the link IN it's
# waiting for in PENDING_LINK_BUILDS
Expand Down
24 changes: 16 additions & 8 deletions ryven-editor/ryven/gui/code_editor/CodePreviewWidget.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from dataclasses import dataclass
from typing import Type, Optional
from typing import Type, Optional, List

from qtpy.QtCore import Qt
from qtpy.QtWidgets import (
Expand All @@ -12,8 +12,8 @@
QGridLayout,
QPushButton
)
from ryvencore import Node

from ryven.gui.main_window import MainWindow
from ryven.gui.code_editor.EditSrcCodeInfoDialog import EditSrcCodeInfoDialog
from ryven.gui.code_editor.CodeEditorWidget import CodeEditorWidget
from ryven.gui.code_editor.SourceCodeUpdater import SrcCodeUpdater
Expand All @@ -29,6 +29,9 @@
load_src_code,
)

from ryvencore import Node
from ryvencore_qt.src.flows.FlowView import FlowView


class LoadSrcCodeButton(QPushButton):
def __init__(self):
Expand All @@ -53,14 +56,14 @@ def __init__(self, name, obj: Inspectable):


class CodePreviewWidget(QWidget):
def __init__(self, main_window, flow_view):
def __init__(self, main_window: MainWindow, flow_view: FlowView):
super().__init__()

self.edits_enabled = main_window.config.src_code_edits_enabled
self.current_insp: Optional[Inspectable] = None

# widgets
self.radio_buttons = []
self.radio_buttons: List[QRadioButton] = []
self.text_edit = CodeEditorWidget(main_window.theme)

self.setup_ui()
Expand Down Expand Up @@ -146,7 +149,9 @@ def _set_node(self, node: Optional[Node]):

def _process_node_src(self, node: Node):
self._rebuild_class_selection(node)
code = class_codes[node.__class__].node_cls
codes = class_codes[node.__class__]
assert codes is not None
code = codes.node_cls
if self.edits_enabled and node in modif_codes:
code = modif_codes[node]
self._update_code(NodeInspectable(node, code))
Expand All @@ -165,11 +170,14 @@ def _update_code(self, insp: Inspectable):


def _rebuild_class_selection(self, node: Node):
assert hasattr(node, 'gui')

self.load_code_button.hide()
self._clear_class_layout()
self.radio_buttons.clear()

codes: NodeTypeCodes = class_codes[node.__class__]
codes = class_codes[node.__class__]
assert codes is not None

def register_rb(rb: QRadioButton):
rb.toggled.connect(self._class_rb_toggled)
Expand Down Expand Up @@ -213,7 +221,7 @@ def _clear_class_layout(self):
widget.hide()
self.class_selection_layout.removeItem(item)

def _load_code_button_clicked(self):
def _load_code_button_clicked(self) -> None:
node: Node = self.sender().node
load_src_code(node.__class__)
self.load_code_button.hide()
Expand All @@ -234,7 +242,7 @@ def _update_radio_buttons_edit_status(self):
f.setBold(False)
br.setFont(f)

def _class_rb_toggled(self, checked):
def _class_rb_toggled(self, checked: bool) -> None:
if checked:
rb: LinkedRadioButton = self.sender()
self._update_code(rb.representing)
Expand Down
9 changes: 5 additions & 4 deletions ryven-editor/ryven/gui/code_editor/SourceCodeUpdater.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import types
from typing import Union
from typing import Union, List


def get_method_funcs(cls_def_str: str, obj):
Expand All @@ -16,15 +16,15 @@ def get_method_funcs(cls_def_str: str, obj):
import ast

# extract functions
ast_funcs: [ast.FunctionDef] = [
ast_funcs: List[ast.FunctionDef] = [
f
for f in ast.parse(cls_def_str).body[0].body
for f in ast.parse(cls_def_str).body[0].body # type: ignore
if type(f) == ast.FunctionDef
]

funcs = {}
for astf in ast_funcs:
d = __builtins__.copy() # important: provide builtins when parsing the function
d = __builtins__.__dict__.copy() # important: provide builtins when parsing the function
exec(ast.unparse(astf), d)
f = d[astf.name]
# # add locals scope of the object to the function
Expand All @@ -47,6 +47,7 @@ def override_code(obj: object, new_class_src) -> Union[None, Exception]:
for name, f in funcs.items(): # override all methods
setattr(obj, name, types.MethodType(f, obj))
# types.MethodType() creates a method bound to obj, from the function f
return None
except Exception as e:
return e

Expand Down
18 changes: 11 additions & 7 deletions ryven-editor/ryven/gui/code_editor/codes_storage.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# statically stores source codes of nodes and their widgets
from dataclasses import dataclass
from typing import Type, Optional
from typing import Type, Optional, Dict, no_type_check
import inspect

from ryvencore import Node
Expand All @@ -13,16 +13,20 @@ def register_node_type(n: Type[Node]):
source code loading is disabled.
"""

assert instance is not None, 'Ryven instance not initialized.'

if not instance.defer_code_loading:
load_src_code(n)
else:
class_codes[n] = None


@no_type_check
def load_src_code(n: Type[Node]):
has_gui = hasattr(n, 'GUI') # check if node type has custom gui
# check for custom GUI and main widget
has_gui = hasattr(n, 'GUI')
has_mw = has_gui and n.GUI.main_widget_class is not None

src = inspect.getsource(n)
mw_src = inspect.getsource(n.GUI.main_widget_class) if has_mw else None
inp_src = {
Expand Down Expand Up @@ -50,7 +54,7 @@ def load_src_code(n: Type[Node]):
class NodeTypeCodes:
node_cls: str
main_widget_cls: Optional[str]
custom_input_widget_clss: {str: str}
custom_input_widget_clss: Dict[str, str]


class Inspectable:
Expand Down Expand Up @@ -79,7 +83,7 @@ class CustomInputWidgetInspectable(Inspectable):
pass


class_codes: {Type[Node]: {}} = {}
class_codes: Dict[Type[Node], Optional[NodeTypeCodes]] = {}
# {
# Type[Node]: NodeTypeCodeInfo
# }
Expand All @@ -91,7 +95,7 @@ class CustomInputWidgetInspectable(Inspectable):


# maps node- or widget classes to their full module source code
mod_codes: {Type: str} = {}
mod_codes: Dict[Type, str] = {}

# maps node- or widget objects to their modified source code
modif_codes: {object: str} = {}
modif_codes: Dict[object, str] = {}
2 changes: 1 addition & 1 deletion ryven-editor/ryven/gui/flow_ui.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ def __init__(self, main_window, flow: Flow, flow_view: FlowView):
self.ui.inspector_dock.setWidget(self.inspector_widget)

#undo history widget
self.undo_widget = QUndoView(self.flow_view._undo_stack)
self.undo_widget = QUndoView(stack=self.flow_view._undo_stack)
self.ui.undo_history_dock.setWidget(self.undo_widget)
# logs
self.ui.logs_scrollArea.setWidget(self.create_loggers_widget())
Expand Down
11 changes: 7 additions & 4 deletions ryven-editor/ryven/gui/main_console.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from typing import Optional, List
import code
import re
import os
Expand Down Expand Up @@ -151,7 +152,9 @@ def push(self, commands: str) -> None:
self.prompt_label.show()

# add leading space for next input
leading_space = re.match(r"\s*", self.buffer[-1]).group()
m = re.match(r"\s*", self.buffer[-1])
assert m is not None
leading_space = m.group()
self.inpedit.next_line = leading_space

else: # no more input required
Expand All @@ -167,9 +170,9 @@ def errorwrite(self, line: str) -> None:
"""capture stderr and print to outdisplay"""
self.writeoutput(line, self.errfmt)

def writeoutput(self, line: str, fmt: QTextCharFormat = None) -> None:
def writeoutput(self, line: str, fmt: Optional[QTextCharFormat] = None) -> None:
"""prints to outdisplay"""
if fmt:
if fmt is not None:
self.out_display.setCurrentCharFormat(fmt)
self.out_display.appendPlainText(line.rstrip())
self.out_display.setCurrentCharFormat(self.outfmt)
Expand All @@ -188,7 +191,7 @@ def __init__(self, code_text_edit, max_history: int = 100):
self.code_text_edit.returned.connect(self.code_text_edit_returned)
self.max_hist = max_history
self.hist_index = 0
self.hist_list = []
self.hist_list: List[str] = []
self.next_line = '' # can be set by console
self.prompt_pattern = re.compile('^[>\.]')

Expand Down
Loading

0 comments on commit 1537aee

Please sign in to comment.