From 1c1be14a9a1e295b0898e46042330ce6847c113d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Celeste=20Sin=C3=A9ad?= Date: Tue, 26 Mar 2024 18:29:27 -0700 Subject: [PATCH] Add config and an optional dependency for type checking with mypy This doesn't actually do a whole lot for existing code, because it's configured not to check the bodies of functions that don't have typed declarations. Meaningful checking can be introduced progressively from here; I started by configuring the pitch tracker modules to require type defs. Individual functions can also be checked, by adding types to their declarations. Unfortunately pyqtProperty isn't type-checkable and does produce errors, so there's a fair amount of error-suppression noise for it :( --- INSTALL.md | 4 +- friture/axis.py | 12 ++--- friture/ballistic_peak.py | 6 +-- friture/bar_label.py | 6 +-- friture/curve.py | 10 ++--- friture/filled_curve.py | 12 ++--- friture/histplot_data.py | 10 ++--- friture/level_data.py | 18 ++++---- friture/level_view_model.py | 28 ++++++------ friture/levels.py | 6 +-- friture/pitch_tracker.py | 58 +++++++++++++------------ friture/pitch_tracker_settings.py | 14 +++--- friture/plotCurve.py | 16 +++---- friture/plotFilledCurve.py | 12 ++--- friture/plotting/coordinateTransform.py | 4 +- friture/plotting/scaleDivision.py | 14 +++--- friture/qml_tools.py | 4 +- friture/scope_data.py | 22 +++++----- friture/spectrum_data.py | 16 +++---- friture/store.py | 6 +-- friture/test/test_IECScale.py | 2 +- friture/test/test_pitch_tracker.py | 6 +-- mypy.ini | 48 ++++++++++++++++++++ pyproject.toml | 6 +++ 24 files changed, 200 insertions(+), 140 deletions(-) create mode 100644 mypy.ini diff --git a/INSTALL.md b/INSTALL.md index 7971421d..2809aad3 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -56,7 +56,7 @@ source ./buildenv/bin/activate 8. Install Friture dependencies (PyQt5, etc.) ``` -pip3.11 install . +pip3.11 install .[dev] ``` 9. Build Cython extensions @@ -114,7 +114,7 @@ virtualenv buildenv 8. Install dependencies ``` -pip install . +pip install .[dev] ``` 9. Build Cython extensions diff --git a/friture/axis.py b/friture/axis.py index 2a848496..55fde661 100644 --- a/friture/axis.py +++ b/friture/axis.py @@ -16,11 +16,11 @@ def __init__(self, parent=None): self._coordinate_transform = CoordinateTransform(-1, 1, 1., 0, 0, self) self._show_minor_grid_lines = False - @pyqtProperty(str, notify=name_changed) + @pyqtProperty(str, notify=name_changed) # type: ignore def name(self): return self._name - @name.setter + @name.setter # type: ignore def name(self, name): if self._name != name: self._name = name @@ -30,7 +30,7 @@ def setTrackerFormatter(self, formatter): if self._formatter != formatter: self._formatter = formatter - @pyqtSlot(float, result=str) + @pyqtSlot(float, result=str) # type: ignore def formatTracker(self, value): return self._formatter(value) @@ -42,11 +42,11 @@ def setScale(self, scale): self._scale_division.setScale(scale) self._coordinate_transform.setScale(scale) - @pyqtProperty(ScaleDivision, constant=True) + @pyqtProperty(ScaleDivision, constant=True) # type: ignore def scale_division(self): return self._scale_division - @pyqtProperty(bool, constant=True) + @pyqtProperty(bool, constant=True) # type: ignore def show_minor_grid_lines(self) -> bool: return self._show_minor_grid_lines @@ -54,6 +54,6 @@ def show_minor_grid_lines(self) -> bool: def show_minor_grid_lines(self, show: bool): self._show_minor_grid_lines = show - @pyqtProperty(CoordinateTransform, constant=True) + @pyqtProperty(CoordinateTransform, constant=True) # type: ignore def coordinate_transform(self): return self._coordinate_transform diff --git a/friture/ballistic_peak.py b/friture/ballistic_peak.py index a8208b41..dbfd3334 100644 --- a/friture/ballistic_peak.py +++ b/friture/ballistic_peak.py @@ -18,7 +18,7 @@ # along with Friture. If not, see . from PyQt5 import QtCore -from PyQt5.QtCore import pyqtProperty +from PyQt5.QtCore import pyqtProperty # type: ignore PEAK_DECAY_RATE = (1.0 - 3E-6/500.) # Number of cycles the peak stays on hold before fall-off. @@ -37,8 +37,8 @@ def __init__(self, parent=None): @pyqtProperty(float, notify=peak_changed) def peak_iec(self): return self._peak_iec - - @peak_iec.setter + + @peak_iec.setter # type: ignore def peak_iec(self, peak_iec): # peak-hold-then-decay mechanism diff --git a/friture/bar_label.py b/friture/bar_label.py index 87299250..cf3096f8 100644 --- a/friture/bar_label.py +++ b/friture/bar_label.py @@ -45,14 +45,14 @@ def setData(self, x, unscaled_x, y): self._y = y self.y_changed.emit() - @pyqtProperty(float, notify=x_changed) + @pyqtProperty(float, notify=x_changed) # type: ignore def x(self): return self._x - @pyqtProperty(float, notify=y_changed) + @pyqtProperty(float, notify=y_changed) # type: ignore def y(self): return self._y - @pyqtProperty(str, notify=unscaled_x_changed) + @pyqtProperty(str, notify=unscaled_x_changed) # type: ignore def unscaled_x(self): return self._unscaled_x diff --git a/friture/curve.py b/friture/curve.py index 5adf5311..298de47a 100644 --- a/friture/curve.py +++ b/friture/curve.py @@ -18,7 +18,7 @@ # along with Friture. If not, see . from PyQt5 import QtCore -from PyQt5.QtCore import pyqtProperty +from PyQt5.QtCore import pyqtProperty # type: ignore import numpy class Curve(QtCore.QObject): @@ -36,18 +36,18 @@ def setData(self, x_array, y_array): self._x_array = x_array self._y_array = y_array self.data_changed.emit() - + def x_array(self): return self._x_array def y_array(self): return self._y_array - + @pyqtProperty(str, notify=name_changed) def name(self): return self._name - - @name.setter + + @name.setter # type: ignore def name(self, name): if self._name != name: self._name = name diff --git a/friture/filled_curve.py b/friture/filled_curve.py index 81ecc1c6..6403a575 100644 --- a/friture/filled_curve.py +++ b/friture/filled_curve.py @@ -49,7 +49,7 @@ def setData(self, x_left_array, x_right_array, y_array, z_array, baseline): self._z_array = z_array self._baseline = baseline self.data_changed.emit() - + def x_left_array(self): return self._x_left_array @@ -64,17 +64,17 @@ def z_array(self): def baseline(self): return self._baseline - + @pyqtProperty(str, notify=name_changed) def name(self): return self._name - - @name.setter + + @name.setter # type: ignore def name(self, name): if self._name != name: self._name = name self.name_changed.emit(name) - - @pyqtProperty(CurveType, constant = True) + + @pyqtProperty(int, constant = True) # type: ignore def curve_type(self): return self._curve_type diff --git a/friture/histplot_data.py b/friture/histplot_data.py index 2c659976..2a86b4e5 100644 --- a/friture/histplot_data.py +++ b/friture/histplot_data.py @@ -19,7 +19,7 @@ from PyQt5 import QtCore from PyQt5.QtCore import pyqtProperty -from PyQt5.QtQml import QQmlListProperty +from PyQt5.QtQml import QQmlListProperty # type: ignore from friture.bar_label import BarLabel from friture.scope_data import Scope_Data @@ -33,7 +33,7 @@ def __init__(self, parent=None): self._bar_labels = [] self._bar_labels_x_distance = 0. - + def setBarLabels(self, x, unscaled_x, y): x_distance = x[1] - x[0] if self._bar_labels_x_distance != x_distance: @@ -41,7 +41,7 @@ def setBarLabels(self, x, unscaled_x, y): self.bar_labels_x_distance_changed.emit() label_count = x.shape[0] - + # never display more than 60 labels # it is not useful visually # and the loop to build them would be too slow @@ -55,10 +55,10 @@ def setBarLabels(self, x, unscaled_x, y): for i in range(label_count): self._bar_labels[i].setData(x[i], unscaled_x[i], y[i]) - @pyqtProperty(QQmlListProperty, notify=bar_labels_changed) + @pyqtProperty(QQmlListProperty, notify=bar_labels_changed) # type: ignore def barLabels(self): return QQmlListProperty(BarLabel, self, self._bar_labels) - @pyqtProperty(float, notify=bar_labels_x_distance_changed) + @pyqtProperty(float, notify=bar_labels_x_distance_changed) # type: ignore def bar_labels_x_distance(self): return self._bar_labels_x_distance diff --git a/friture/level_data.py b/friture/level_data.py index a7e84b54..14a07734 100644 --- a/friture/level_data.py +++ b/friture/level_data.py @@ -32,29 +32,29 @@ def __init__(self, parent=None): self._level_rms = -30. self._level_max = -30. - @pyqtProperty(float, notify=level_rms_changed) + @pyqtProperty(float, notify=level_rms_changed) # type: ignore def level_rms(self): return self._level_rms - - @pyqtProperty(float, notify=level_rms_changed) + + @pyqtProperty(float, notify=level_rms_changed) # type: ignore def level_rms_iec(self): return dB_to_IEC(self._level_rms) - - @level_rms.setter + + @level_rms.setter # type: ignore def level_rms(self, level_rms): if self._level_rms != level_rms: self._level_rms = level_rms self.level_rms_changed.emit(level_rms) - @pyqtProperty(float, notify=level_max_changed) + @pyqtProperty(float, notify=level_max_changed) # type: ignore def level_max(self): return self._level_max - @pyqtProperty(float, notify=level_max_changed) + @pyqtProperty(float, notify=level_max_changed) # type: ignore def level_max_iec(self): return dB_to_IEC(self._level_max) - - @level_max.setter + + @level_max.setter # type: ignore def level_max(self, level_max): if self._level_max != level_max: self._level_max = level_max diff --git a/friture/level_view_model.py b/friture/level_view_model.py index 87b476f9..f30c0194 100644 --- a/friture/level_view_model.py +++ b/friture/level_view_model.py @@ -38,36 +38,36 @@ def __init__(self, parent=None): self._level_data_ballistic = BallisticPeak(self) self._level_data_ballistic_2 = BallisticPeak(self) - @pyqtProperty(bool, notify=two_channels_changed) + @pyqtProperty(bool, notify=two_channels_changed) # type: ignore def two_channels(self): return self._two_channels - - @two_channels.setter + + @two_channels.setter # type: ignore def two_channels(self, two_channels): if self._two_channels != two_channels: self._two_channels = two_channels self.two_channels_changed.emit(two_channels) - @pyqtProperty(LevelData, constant = True) + @pyqtProperty(LevelData, constant = True) # type: ignore def level_data(self): return self._level_data - - @pyqtProperty(LevelData, constant = True) + + @pyqtProperty(LevelData, constant = True) # type: ignore def level_data_2(self): return self._level_data_2 - - @pyqtProperty(LevelData, constant = True) + + @pyqtProperty(LevelData, constant = True) # type: ignore def level_data_slow(self): return self._level_data_slow - - @pyqtProperty(LevelData, constant = True) + + @pyqtProperty(LevelData, constant = True) # type: ignore def level_data_slow_2(self): return self._level_data_slow_2 - - @pyqtProperty(LevelData, constant = True) + + @pyqtProperty(LevelData, constant = True) # type: ignore def level_data_ballistic(self): return self._level_data_ballistic - - @pyqtProperty(LevelData, constant = True) + + @pyqtProperty(LevelData, constant = True) # type: ignore def level_data_ballistic_2(self): return self._level_data_ballistic_2 \ No newline at end of file diff --git a/friture/levels.py b/friture/levels.py index 1a05f0ed..d41c58a5 100644 --- a/friture/levels.py +++ b/friture/levels.py @@ -21,7 +21,7 @@ from PyQt5 import QtWidgets from PyQt5.QtQml import QQmlComponent -from PyQt5.QtQuick import QQuickWindow +from PyQt5.QtQuick import QQuickWindow # type: ignore import numpy as np from friture.store import GetStore @@ -62,7 +62,7 @@ def __init__(self, parent, engine): self.qmlObject.setParent(self.quickWindow) self.quickWidget = QtWidgets.QWidget.createWindowContainer(self.quickWindow, self) - self.quickWidget.setSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Expanding) + self.quickWidget.setSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Expanding) self.gridLayout.addWidget(self.quickWidget) self.qmlObject.widthChanged.connect(self.onWidthChanged) @@ -174,7 +174,7 @@ def canvasUpdate(self): if self.two_channels: self.level_view_model.level_data_slow_2.level_rms = self.level_view_model.level_data_2.level_rms self.level_view_model.level_data_slow_2.level_max = self.level_view_model.level_data_2.level_max - + self.i = self.i % LEVEL_TEXT_LABEL_STEPS # slot diff --git a/friture/pitch_tracker.py b/friture/pitch_tracker.py index f1ff860e..f918d7c7 100644 --- a/friture/pitch_tracker.py +++ b/friture/pitch_tracker.py @@ -27,9 +27,10 @@ from PyQt5.QtQuick import QQuickWindow from PyQt5.QtQuickWidgets import QQuickWidget from PyQt5.QtQml import QQmlComponent, QQmlEngine -from typing import Any, no_type_check, Optional +from typing import Any, Optional from friture.audiobackend import SAMPLING_RATE +from friture.audiobuffer import AudioBuffer from friture.audioproc import audioproc from friture.curve import Curve from friture.pitch_tracker_settings import ( @@ -66,7 +67,7 @@ def format_frequency(freq: float) -> str: class PitchTrackerWidget(QtWidgets.QWidget): - def __init__(self, parent, engine): + def __init__(self, parent: QtWidgets.QWidget, engine: QQmlEngine): super().__init__(parent) self.logger = logging.getLogger(__name__) @@ -79,15 +80,17 @@ def __init__(self, parent, engine): state_id = len(store._dock_states) - 1 self._curve = Curve() - self._curve.name = "Ch1" + self._curve.name = "Ch1" # type: ignore [assignment] self._pitch_tracker_data.add_plot_item(self._curve) - self._pitch_tracker_data.vertical_axis.name = "Frequency (Hz)" - self._pitch_tracker_data.vertical_axis.setTrackerFormatter( + # cast for qt properties which aren't properly typed + tracker_data: Any = self._pitch_tracker_data + tracker_data.vertical_axis.name = "Frequency (Hz)" + tracker_data.vertical_axis.setTrackerFormatter( format_frequency) - self._pitch_tracker_data.vertical_axis.show_minor_grid_lines = True - self._pitch_tracker_data.horizontal_axis.name = "Time (sec)" - self._pitch_tracker_data.horizontal_axis.setTrackerFormatter( + tracker_data.vertical_axis.show_minor_grid_lines = True + tracker_data.horizontal_axis.name = "Time (sec)" + tracker_data.horizontal_axis.setTrackerFormatter( lambda x: "%#.3g sec" % (x)) self.gridLayout = QtWidgets.QGridLayout(self) @@ -103,7 +106,8 @@ def __init__(self, parent, engine): raise_if_error(self.quickWidget) - self.quickWidget.rootObject().setProperty("stateId", state_id) + root: Any = self.quickWidget.rootObject() + root.setProperty("stateId", state_id) self.gridLayout.addWidget(self.quickWidget, 0, 0, 1, 1) @@ -111,7 +115,7 @@ def __init__(self, parent, engine): pitch_window = QQuickWindow() pitch_component = QQmlComponent(engine, qml_url("PitchView.qml"), self) raise_if_error(pitch_component) - pitch_object = pitch_component.createWithInitialProperties( + pitch_object: Any = pitch_component.createWithInitialProperties( { "parent": pitch_window.contentItem(), "pitch_view_model": self.pitch_view_model @@ -141,65 +145,65 @@ def __init__(self, parent, engine): self.settings_dialog = PitchTrackerSettingsDialog(self) - self.audiobuffer = None + self.audiobuffer: Optional[AudioBuffer] = None self.tracker = PitchTracker(RingBuffer()) self.update_curve() # method - def set_buffer(self, buffer): + def set_buffer(self, buffer: AudioBuffer) -> None: self.audiobuffer = buffer self.tracker.set_input_buffer(buffer.ringbuffer) - def handle_new_data(self, floatdata): + def handle_new_data(self, floatdata: np.ndarray) -> None: if self.tracker.update(): self.update_curve() self.pitch_view_model.pitch = self.tracker.get_latest_estimate() # type: ignore - def update_curve(self): + def update_curve(self) -> None: pitches = self.tracker.get_estimates(self.duration) - pitches = 1.0 - self.vertical_transform.toScreen(pitches) + pitches = 1.0 - self.vertical_transform.toScreen(pitches) # type: ignore pitches = np.clip(pitches, 0, 1) times = np.linspace(0, 1.0, pitches.shape[0]) self._curve.setData(times, pitches) - def on_status_changed(self, status): + def on_status_changed(self, status: QQuickWidget.Status) -> None: if status == QQuickWidget.Error: for error in self.quickWidget.errors(): self.logger.error("QML error: " + error.toString()) # method - def canvasUpdate(self): + def canvasUpdate(self) -> None: # nothing to do here return - def set_min_freq(self, value): + def set_min_freq(self, value: int) -> None: self.min_freq = value - self._pitch_tracker_data.vertical_axis.setRange(self.min_freq, self.max_freq) + self._pitch_tracker_data.vertical_axis.setRange(self.min_freq, self.max_freq) # type: ignore self.vertical_transform.setRange(self.min_freq, self.max_freq) - def set_max_freq(self, value): + def set_max_freq(self, value: int) -> None: self.max_freq = value - self._pitch_tracker_data.vertical_axis.setRange(self.min_freq, self.max_freq) + self._pitch_tracker_data.vertical_axis.setRange(self.min_freq, self.max_freq) # type: ignore self.vertical_transform.setRange(self.min_freq, self.max_freq) - def set_duration(self, value): + def set_duration(self, value: int) -> None: self.duration = value - self._pitch_tracker_data.horizontal_axis.setRange(-self.duration, 0.) + self._pitch_tracker_data.horizontal_axis.setRange(-self.duration, 0.) # type: ignore - def set_min_db(self, value: float): + def set_min_db(self, value: float) -> None: self.tracker.min_db = value # slot - def settings_called(self, checked): + def settings_called(self, checked: bool) -> None: self.settings_dialog.show() # method - def saveState(self, settings): + def saveState(self, settings: QSettings) -> None: self.settings_dialog.save_state(settings) # method - def restoreState(self, settings): + def restoreState(self, settings: QSettings) -> None: self.settings_dialog.restore_state(settings) diff --git a/friture/pitch_tracker_settings.py b/friture/pitch_tracker_settings.py index 7c4a6850..89f9b89b 100644 --- a/friture/pitch_tracker_settings.py +++ b/friture/pitch_tracker_settings.py @@ -18,6 +18,8 @@ # along with Friture. If not, see . from PyQt5 import QtWidgets +from PyQt5.QtCore import QSettings +from typing import Any from friture.audiobackend import SAMPLING_RATE @@ -29,7 +31,7 @@ DEFAULT_FFT_SIZE = 16384 class PitchTrackerSettingsDialog(QtWidgets.QDialog): - def __init__(self, parent): + def __init__(self, parent: QtWidgets.QWidget) -> None: super().__init__(parent) self.setWindowTitle("Pitch Tracker Settings") self.form_layout = QtWidgets.QFormLayout(self) @@ -41,7 +43,7 @@ def __init__(self, parent): self.min_freq.setValue(DEFAULT_MIN_FREQ) self.min_freq.setSuffix(" Hz") self.min_freq.setObjectName("min_freq") - self.min_freq.valueChanged.connect(self.parent().set_min_freq) + self.min_freq.valueChanged.connect(self.parent().set_min_freq) # type: ignore self.form_layout.addRow("Min:", self.min_freq) self.max_freq = QtWidgets.QSpinBox(self) @@ -51,7 +53,7 @@ def __init__(self, parent): self.max_freq.setValue(DEFAULT_MAX_FREQ) self.max_freq.setSuffix(" Hz") self.max_freq.setObjectName("max_freq") - self.max_freq.valueChanged.connect(self.parent().set_max_freq) + self.max_freq.valueChanged.connect(self.parent().set_max_freq) # type: ignore self.form_layout.addRow("Max:", self.max_freq) self.duration = QtWidgets.QSpinBox(self) @@ -61,7 +63,7 @@ def __init__(self, parent): self.duration.setValue(DEFAULT_DURATION) self.duration.setSuffix(" s") self.duration.setObjectName("duration") - self.duration.valueChanged.connect(self.parent().set_duration) + self.duration.valueChanged.connect(self.parent().set_duration) # type: ignore self.form_layout.addRow("Duration:", self.duration) self.min_db = QtWidgets.QDoubleSpinBox(self) @@ -76,13 +78,13 @@ def __init__(self, parent): self.setLayout(self.form_layout) - def save_state(self, settings): + def save_state(self, settings: QSettings) -> None: settings.setValue("min_freq", self.min_freq.value()) settings.setValue("max_freq", self.max_freq.value()) settings.setValue("duration", self.duration.value()) settings.setValue("min_db", self.min_db.value()) - def restore_state(self, settings): + def restore_state(self, settings: QSettings) -> None: self.min_freq.setValue( settings.value("min_freq", DEFAULT_MIN_FREQ, type=int)) self.max_freq.setValue( diff --git a/friture/plotCurve.py b/friture/plotCurve.py index 2463c0e5..ebf4e922 100644 --- a/friture/plotCurve.py +++ b/friture/plotCurve.py @@ -1,7 +1,7 @@ import numpy as np -from PyQt5.QtCore import pyqtSignal, pyqtProperty +from PyQt5.QtCore import pyqtSignal, pyqtProperty # type: ignore from PyQt5.QtGui import QColor -from PyQt5.QtQuick import QQuickItem, QSGGeometryNode, QSGGeometry, QSGFlatColorMaterial, QSGNode +from PyQt5.QtQuick import QQuickItem, QSGGeometryNode, QSGGeometry, QSGFlatColorMaterial, QSGNode # type: ignore from friture.curve import Curve @@ -24,24 +24,24 @@ def __init__(self, parent = None): def color(self): return self._color - @color.setter + @color.setter # type: ignore def color(self, color): if color != self._color: self._color = color self.update() self.colorChanged.emit() - + @pyqtProperty(Curve, notify=curveChanged) def curve(self): return self._curve - @curve.setter + @curve.setter # type: ignore def curve(self, curve): if curve != self._curve: self._curve = curve if self._curve is not None: self._curve.data_changed.connect(self.update) - + self.update() self.curveChanged.emit() @@ -60,7 +60,7 @@ def updatePaintNode(self, paint_node, update_data): paint_node.setMaterial(material) paint_node.setFlag(QSGNode.OwnsMaterial) paint_node.markDirty(QSGNode.DirtyMaterial) - + else: geometry = paint_node.geometry() geometry.allocate(self.curve.x_array().size) # geometry will be marked as dirty below @@ -69,7 +69,7 @@ def updatePaintNode(self, paint_node, update_data): if material.color() != self._color: material.setColor(self._color) paint_node.markDirty(QSGNode.DirtyMaterial) - + size = self.curve.x_array().size # ideally we would use geometry.vertexDataAsPoint2D diff --git a/friture/plotFilledCurve.py b/friture/plotFilledCurve.py index 09e431f9..a6cc384c 100644 --- a/friture/plotFilledCurve.py +++ b/friture/plotFilledCurve.py @@ -19,8 +19,8 @@ import numpy as np -from PyQt5.QtCore import pyqtSignal, pyqtProperty -from PyQt5.QtQuick import QQuickItem, QSGGeometryNode, QSGGeometry, QSGNode, QSGVertexColorMaterial +from PyQt5.QtCore import pyqtSignal, pyqtProperty # type: ignore +from PyQt5.QtQuick import QQuickItem, QSGGeometryNode, QSGGeometry, QSGNode, QSGVertexColorMaterial # type: ignore from friture.filled_curve import CurveType, FilledCurve @@ -33,18 +33,18 @@ def __init__(self, parent = None): self.setFlag(QQuickItem.ItemHasContents, True) self._curve = FilledCurve(CurveType.SIGNAL) - + @pyqtProperty(FilledCurve, notify=curveChanged) def curve(self): return self._curve - @curve.setter + @curve.setter # type: ignore def curve(self, curve): if curve != self._curve: self._curve = curve if self._curve is not None: self._curve.data_changed.connect(self.update) - + self.update() self.curveChanged.emit() @@ -98,7 +98,7 @@ def updatePaintNode(self, paint_node, update_data): # a custom structured data type that represents the vertex data is interpreted vertex_dtype = np.dtype([('x', np.float32), ('y', np.float32), ('r', np.ubyte), ('g', np.ubyte), ('b', np.ubyte), ('a', np.ubyte)]) vertex_data.setsize(vertex_dtype.itemsize * vertex_count) - + vertices = np.frombuffer(vertex_data, dtype=vertex_dtype) baseline = self.curve.baseline() * self.height() + 0.*y diff --git a/friture/plotting/coordinateTransform.py b/friture/plotting/coordinateTransform.py index 607c18fd..748ba4bd 100644 --- a/friture/plotting/coordinateTransform.py +++ b/friture/plotting/coordinateTransform.py @@ -58,7 +58,7 @@ def setBorders(self, start, end): def setScale(self, scale): self.scale = scale - @pyqtSlot(float, result=float) + @pyqtSlot(float, result=float) # type: ignore def toScreen(self, x): if self.scale is fscales.Logarithmic: if self.coord_clipped_min == self.coord_clipped_max: @@ -82,7 +82,7 @@ def toScreen(self, x): / (trans_max - trans_min) + self.startBorder) - @pyqtSlot(float, result=float) + @pyqtSlot(float, result=float) # type: ignore def toPlot(self, x): if self.length == self.startBorder + self.endBorder: return self.coord_min + 0. * x # keep x type (this can produce a RunTimeWarning if x contains inf) diff --git a/friture/plotting/scaleDivision.py b/friture/plotting/scaleDivision.py index 37922d3d..6aa180a4 100644 --- a/friture/plotting/scaleDivision.py +++ b/friture/plotting/scaleDivision.py @@ -19,7 +19,7 @@ from PyQt5 import QtCore from PyQt5.QtCore import pyqtProperty -from PyQt5.QtQml import QQmlListProperty +from PyQt5.QtQml import QQmlListProperty # type: ignore import friture.plotting.frequency_scales as fscales from friture.plotting.coordinateTransform import CoordinateTransform @@ -29,12 +29,12 @@ def __init__(self, value, logical_value, parent=None): super().__init__(parent) self._value = value self._logical_value = logical_value - - @pyqtProperty(str, constant = True) + + @pyqtProperty(str, constant = True) # type: ignore def value(self): return self._value - @pyqtProperty(float, constant = True) + @pyqtProperty(float, constant = True) # type: ignore def logicalValue(self): return self._logical_value @@ -65,15 +65,15 @@ def setScale(self, scale): def majorTicks(self): return self.major_ticks - + def minorTicks(self): return self.minor_ticks - @pyqtProperty(QQmlListProperty, notify=logical_major_ticks_changed) + @pyqtProperty(QQmlListProperty, notify=logical_major_ticks_changed) # type: ignore def logicalMajorTicks(self): return QQmlListProperty(Tick, self, self._logical_major_ticks) - @pyqtProperty(QQmlListProperty, notify=logical_minor_ticks_changed) + @pyqtProperty(QQmlListProperty, notify=logical_minor_ticks_changed) # type: ignore def logicalMinorTicks(self): return QQmlListProperty(Tick, self, self._logical_minor_ticks) diff --git a/friture/qml_tools.py b/friture/qml_tools.py index 80610758..177c992b 100644 --- a/friture/qml_tools.py +++ b/friture/qml_tools.py @@ -8,10 +8,10 @@ def qml_url(fileName): return QUrl.fromLocalFile(qml_path(fileName)) def qml_path(fileName): - # https://pyinstaller.readthedocs.io/en/stable/runtime-information.html + # https://pyinstaller.readthedocs.io/en/stable/runtime-information.html if getattr(sys, 'frozen', False): # If the application is run as a bundle, the PyInstaller bootloader - # extends the sys module by a flag frozen=True and sets the app + # extends the sys module by a flag frozen=True and sets the app # path into variable _MEIPASS'. application_path = sys._MEIPASS else: diff --git a/friture/scope_data.py b/friture/scope_data.py index 24ec49b1..511ec06a 100644 --- a/friture/scope_data.py +++ b/friture/scope_data.py @@ -19,13 +19,13 @@ from PyQt5 import QtCore from PyQt5.QtCore import pyqtProperty -from PyQt5.QtQml import QQmlListProperty +from PyQt5.QtQml import QQmlListProperty # type: ignore from friture.axis import Axis from friture.curve import Curve class Scope_Data(QtCore.QObject): - show_legend_changed = QtCore.pyqtSignal(bool) + show_legend_changed = QtCore.pyqtSignal(bool) plot_items_changed = QtCore.pyqtSignal() def __init__(self, parent=None): @@ -36,14 +36,14 @@ def __init__(self, parent=None): self._vertical_axis = Axis(self) self._show_legend = True - @pyqtProperty(QQmlListProperty, notify=plot_items_changed) + @pyqtProperty(QQmlListProperty, notify=plot_items_changed) # type: ignore def plot_items(self): return QQmlListProperty(Curve, self, self._plot_items) - + def insert_plot_item(self, index, plot_item): self._plot_items.insert(index, plot_item) self.plot_items_changed.emit() - + def add_plot_item(self, plot_item): self._plot_items.append(plot_item) plot_item.setParent(self) # take ownership @@ -53,19 +53,19 @@ def remove_plot_item(self, plot_item): self._plot_items.remove(plot_item) self.plot_items_changed.emit() - @pyqtProperty(Axis, constant=True) + @pyqtProperty(Axis, constant=True) # type: ignore def horizontal_axis(self): return self._horizontal_axis - @pyqtProperty(Axis, constant=True) + @pyqtProperty(Axis, constant=True) # type: ignore def vertical_axis(self): return self._vertical_axis - - @pyqtProperty(bool, notify=show_legend_changed) + + @pyqtProperty(bool, notify=show_legend_changed) # type: ignore def show_legend(self): return self._show_legend - - @show_legend.setter + + @show_legend.setter # type: ignore def show_legend(self, show_legend): if self._show_legend != show_legend: self._show_legend = show_legend diff --git a/friture/spectrum_data.py b/friture/spectrum_data.py index 75b0b22d..b4be3ad5 100644 --- a/friture/spectrum_data.py +++ b/friture/spectrum_data.py @@ -38,11 +38,11 @@ def __init__(self, parent=None): self._show_frequency_tracker = True self._show_pitch_tracker = True - @pyqtProperty(str, notify=fmax_changed) + @pyqtProperty(str, notify=fmax_changed) # type: ignore def fmaxValue(self): return self._fmax_value - @pyqtProperty(float, notify=fmax_changed) + @pyqtProperty(float, notify=fmax_changed) # type: ignore def fmaxLogicalValue(self): return self._fmax_logical_value @@ -52,11 +52,11 @@ def setFmax(self, value, logical_value): self._fmax_logical_value = logical_value self.fmax_changed.emit() - @pyqtProperty(str, notify=fpitch_changed) + @pyqtProperty(str, notify=fpitch_changed) # type: ignore def fpitchDisplayText(self): return self._fpitch_display - @pyqtProperty(float, notify=fpitch_changed) + @pyqtProperty(float, notify=fpitch_changed) # type: ignore def fpitchValue(self): return self._fpitch_value @@ -66,21 +66,21 @@ def setFpitch(self, display_text, value): self._fpitch_value = value self.fpitch_changed.emit() - @pyqtProperty(bool, notify=show_frequency_tracker_changed) + @pyqtProperty(bool, notify=show_frequency_tracker_changed) # type: ignore def showFrequencyTracker(self): return self._show_frequency_tracker - @showFrequencyTracker.setter + @showFrequencyTracker.setter # type: ignore def showFrequencyTracker(self, show_frequency_tracker): if self._show_frequency_tracker != show_frequency_tracker: self._show_frequency_tracker = show_frequency_tracker self.show_frequency_tracker_changed.emit(show_frequency_tracker) - @pyqtProperty(bool, notify=show_pitch_tracker_changed) + @pyqtProperty(bool, notify=show_pitch_tracker_changed) # type: ignore def showPitchTracker(self): return self._show_pitch_tracker - @showPitchTracker.setter + @showPitchTracker.setter # type: ignore def showPitchTracker(self, show_pitch_tracker): if self._show_pitch_tracker != show_pitch_tracker: self._show_pitch_tracker = show_pitch_tracker diff --git a/friture/store.py b/friture/store.py index 72d15be2..3ce5058b 100644 --- a/friture/store.py +++ b/friture/store.py @@ -1,6 +1,6 @@ from PyQt5 import QtCore from PyQt5.QtCore import QObject, pyqtProperty -from PyQt5.QtQml import QQmlListProperty +from PyQt5.QtQml import QQmlListProperty # type: ignore __storeInstance = None @@ -16,7 +16,7 @@ class Store(QtCore.QObject): def __init__(self, parent=None): super().__init__(parent) self._dock_states = [] - - @pyqtProperty(QQmlListProperty, notify=dock_states_changed) + + @pyqtProperty(QQmlListProperty, notify=dock_states_changed) # type: ignore def dock_states(self): return QQmlListProperty(QObject, self, self._dock_states) diff --git a/friture/test/test_IECScale.py b/friture/test/test_IECScale.py index e9354456..813e6ec1 100644 --- a/friture/test/test_IECScale.py +++ b/friture/test/test_IECScale.py @@ -5,7 +5,7 @@ import sys sys.path.insert(0, '.') - import friture.qsynthmeter as meter + import friture.qsynthmeter as meter # type: ignore scale = meter.IECScale() diff --git a/friture/test/test_pitch_tracker.py b/friture/test/test_pitch_tracker.py index 15e7218f..660ef1c1 100644 --- a/friture/test/test_pitch_tracker.py +++ b/friture/test/test_pitch_tracker.py @@ -24,7 +24,7 @@ from friture.ringbuffer import RingBuffer class PitchTrackerTest(unittest.TestCase): - def test_new_frames(self): + def test_new_frames(self) -> None: buf = RingBuffer() tracker = PitchTracker(buf, fft_size=4, overlap=0.5) buf.push(np.array([np.arange(2)])) @@ -39,7 +39,7 @@ def test_new_frames(self): list(tracker.new_frames()) ) - def test_estimate_pitch(self): + def test_estimate_pitch(self) -> None: buf = RingBuffer() tracker = PitchTracker(buf, fft_size=32, overlap=0.5) # use inverse fft to synthesize a signal where the first harmonic has @@ -51,7 +51,7 @@ def test_estimate_pitch(self): ])) self.assertEqual(pitch, 3000) - def test_update(self): + def test_update(self) -> None: buf = RingBuffer() tracker = PitchTracker(buf, fft_size=32, overlap=0.5, sample_rate=32) diff --git a/mypy.ini b/mypy.ini new file mode 100644 index 00000000..ee78a502 --- /dev/null +++ b/mypy.ini @@ -0,0 +1,48 @@ +[mypy] +packages = friture +show_error_codes = true + +# Effectively disable type checking for most code by disabling checking of +# untyped functions, and not requiring functions to be typed, by default. Type +# checking can be enabled by adding types to function definitions, or by making +# type declarations mandatory in specific modules, below +disallow_untyped_defs = false +check_untyped_defs = false + +[mypy-friture.pitch_tracker] +disallow_untyped_defs = true + +[mypy-friture.pitch_tracker_settings] +disallow_untyped_defs = true + +[mypy-friture.test.test_pitch_tracker] +disallow_untyped_defs = true + +# Missing or incomplete type stubs: + +[mypy-appdirs.*] +ignore_missing_imports = true + +[mypy-friture_extensions.*] +ignore_missing_imports = true + +[mypy-lsprofcalltree] +ignore_missing_imports = true + +[mypy-matplotlib.*] +ignore_missing_imports = true + +[mypy-PyQt5.QtQuickWidgets] +ignore_missing_imports = true + +[mypy-PyQt5.QtQml] +ignore_missing_imports = true + +[mypy-rtmixer] +ignore_missing_imports = true + +[mypy-scipy.*] +ignore_missing_imports = true + +[mypy-sounddevice] +ignore_missing_imports = true diff --git a/pyproject.toml b/pyproject.toml index b19153a6..be4c9e22 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -41,6 +41,12 @@ dependencies = [ "pyrr==0.10.3", ] +[project.optional-dependencies] +dev = [ + "mypy==1.10.0", + "PyQt5-stubs==5.15.6.0", +] + [project.scripts] friture = "friture.analyzer:main"