Use Mypy checking (#154)

Major changes:
- LayoutConfig._calc_layout() improved
- register_enum() replaced with DumpEnumAsStr (*very* iffy multiple inheritance)

i configured mypy to not care about missing annotations...
but most files are fully annotated via MonkeyType = big PR diff.
pull/357/head
nyanpasu64 2019-01-25 22:50:19 -08:00 zatwierdzone przez GitHub
rodzic e3cddd7e69
commit 9160958257
26 zmienionych plików z 272 dodań i 161 usunięć

3
.gitignore vendored
Wyświetl plik

@ -189,3 +189,6 @@ setup.py
# Corrscope pyinstaller version codes
_version.py
version.txt
/annotations.json
/monkeytype.sqlite3
/err

Wyświetl plik

@ -46,6 +46,7 @@ build_script:
test_script:
- 'poetry run pytest --tb=short --cov=corrscope'
- 'poetry run codecov'
- 'poetry run mypy corrscope'
after_test:
- 'if not "%pydir%"=="C:\Python37-x64" appveyor exit'

Wyświetl plik

@ -1,5 +1,5 @@
from os.path import abspath
from typing import TYPE_CHECKING, Optional, Union
from typing import TYPE_CHECKING, Optional, Union, Dict, Any
import attr
from ruamel.yaml.comments import CommentedMap
@ -17,7 +17,7 @@ class ChannelConfig(DumpableAttrs):
wav_path: str
# Supplying a dict inherits attributes from global trigger.
trigger: Union[ITriggerConfig, dict, None] = attr.Factory(
trigger: Union[ITriggerConfig, Dict[str, Any], None] = attr.Factory( # type: ignore
dict
) # TODO test channel-specific triggers
# Multiplies how wide the window is, in milliseconds.

Wyświetl plik

@ -2,7 +2,7 @@ import datetime
import sys
from itertools import count
from pathlib import Path
from typing import Optional, List, Tuple, Union, Iterator
from typing import Optional, List, Tuple, Union, Iterator, cast
import click
@ -83,7 +83,7 @@ CONTEXT_SETTINGS = dict(help_option_names=["-h", "--help"])
@click.option('--profile', is_flag=True, help=
'Debug: Write CProfiler snapshot')
@click.version_option(corrscope.__version__)
# fmt: on
# fmt: on is ignored, because of https://github.com/ambv/black/issues/560
def main(
files: Tuple[str],
# cfg
@ -116,6 +116,8 @@ def main(
show_gui = not any([write, play, render])
# Gather data for cfg: Config object.
CfgOrPath = Union[Config, Path]
cfg_or_path: Union[Config, Path, None] = None
cfg_dir: Optional[str] = None
@ -171,10 +173,11 @@ def main(
cfg_dir = '.'
assert cfg_or_path is not None
assert cfg_dir is not None
if show_gui:
def command():
from corrscope import gui
return gui.gui_main(cfg_or_path)
return gui.gui_main(cast(CfgOrPath, cfg_or_path))
if profile:
import cProfile
@ -227,18 +230,20 @@ def main(
except MissingFFmpegError as e:
# Tell user how to install ffmpeg (__str__).
print(e, file=sys.stderr)
# fmt: on
def get_profile_dump_name(prefix: str) -> str:
now = datetime.datetime.now()
now = now.strftime('%Y-%m-%d_T%H-%M-%S')
now_date = datetime.datetime.now()
now_str = now_date.strftime("%Y-%m-%d_T%H-%M-%S")
profile_dump_name = f'{prefix}-{PROFILE_DUMP_NAME}-{now}'
profile_dump_name = f"{prefix}-{PROFILE_DUMP_NAME}-{now_str}"
# Write stats to unused filename
for path in add_numeric_suffixes(profile_dump_name):
if not Path(path).exists():
return path
assert False # never happens since add_numeric_suffixes is endless.
def add_numeric_suffixes(s: str) -> Iterator[str]:

Wyświetl plik

@ -1,7 +1,7 @@
import pickle
from enum import Enum
from io import StringIO, BytesIO
from typing import ClassVar, TYPE_CHECKING, Type, TypeVar, Set
from typing import *
import attr
from ruamel.yaml import (
@ -13,6 +13,9 @@ from ruamel.yaml import (
Node,
)
if TYPE_CHECKING:
from pathlib import Path
__all__ = [
"yaml",
"copy_config",
@ -20,7 +23,7 @@ __all__ = [
"KeywordAttrs",
"Alias",
"Ignored",
"register_enum",
"DumpEnumAsStr",
"TypedEnumDump",
"CorrError",
"CorrWarning",
@ -31,15 +34,18 @@ __all__ = [
class MyYAML(YAML):
def dump(self, data, stream=None, **kw):
def dump(
self, data: Any, stream: "Union[Path, TextIO, None]" = None, **kwargs
) -> Optional[str]:
""" Allow dumping to str. """
inefficient = False
if stream is None:
inefficient = True
stream = StringIO()
YAML.dump(self, data, stream, **kw)
YAML.dump(self, data, stream, **kwargs)
if inefficient:
return stream.getvalue()
return cast(StringIO, stream).getvalue()
return None
class NoAliasRepresenter(RoundTripRepresenter):
@ -49,7 +55,7 @@ class NoAliasRepresenter(RoundTripRepresenter):
TODO test
"""
def ignore_aliases(self, data):
def ignore_aliases(self, data: Any) -> bool:
if isinstance(data, Enum):
return True
return super().ignore_aliases(data)
@ -109,7 +115,7 @@ class DumpableAttrs:
def __init__(self, *args, **kwargs):
pass
def __init_subclass__(cls, kw_only=False, always_dump: str = ""):
def __init_subclass__(cls, kw_only: bool = False, always_dump: str = ""):
cls.__always_dump = set(always_dump.split())
del always_dump
@ -129,7 +135,7 @@ class DumpableAttrs:
), f'Invalid always_dump="...{dump_field}" missing from class {cls.__name__}'
# SafeRepresenter.represent_yaml_object() uses __getstate__ to dump objects.
def __getstate__(self):
def __getstate__(self) -> Dict[str, Any]:
""" Removes all fields with default values, but not found in
self.always_dump. """
@ -157,8 +163,8 @@ class DumpableAttrs:
continue
# noinspection PyTypeChecker,PyUnresolvedReferences
if (
isinstance(field.default, attr.Factory)
and field.default.factory() == value
isinstance(field.default, attr.Factory) # type: ignore
and field.default.factory() == value # type: ignore
):
continue
@ -167,7 +173,7 @@ class DumpableAttrs:
return state
# SafeConstructor.construct_yaml_object() uses __setstate__ to load objects.
def __setstate__(self, state):
def __setstate__(self, state: Dict[str, Any]) -> None:
""" Redirect `Alias(key)=value` to `key=value`.
Then call the dataclass constructor (to validate parameters). """
@ -217,29 +223,30 @@ Ignored = object()
# Setup Enum load/dump infrastructure
SomeEnum = TypeVar("SomeEnum", bound=Enum)
def register_enum(cls: Type):
cls.to_yaml = _EnumMixin.to_yaml
return _yaml_loadable(cls)
class _EnumMixin:
@classmethod
def to_yaml(cls, representer: Representer, node: Enum):
return representer.represent_str(node._name_)
class TypedEnumDump:
def __init_subclass__(cls, **kwargs):
class DumpEnumAsStr(Enum):
def __init_subclass__(cls):
_yaml_loadable(cls)
@classmethod
def to_yaml(cls: Type[Enum], representer: Representer, node: Enum):
return representer.represent_scalar("!" + cls.__name__, node._name_)
def to_yaml(cls, representer: Representer, node: Enum) -> Any:
return representer.represent_str(node._name_) # type: ignore
class TypedEnumDump(Enum):
def __init_subclass__(cls):
_yaml_loadable(cls)
@classmethod
def from_yaml(cls: Type[Enum], constructor: Constructor, node: Node):
def to_yaml(cls, representer: Representer, node: Enum) -> Any:
return representer.represent_scalar(
"!" + cls.__name__, node._name_ # type: ignore
)
@classmethod
def from_yaml(cls, constructor: Constructor, node: Node) -> Enum:
return cls[node.value]

Wyświetl plik

@ -1,33 +1,39 @@
# -*- coding: utf-8 -*-
import time
from contextlib import ExitStack, contextmanager
from enum import unique, IntEnum
from enum import unique, Enum
from fractions import Fraction
from pathlib import Path
from types import SimpleNamespace
from typing import Optional, List, Union, TYPE_CHECKING, Callable
from typing import Iterator
from typing import Optional, List, Union, Callable, cast
import attr
from corrscope import outputs as outputs_
from corrscope.channel import Channel, ChannelConfig
from corrscope.config import KeywordAttrs, register_enum, CorrError
from corrscope.config import KeywordAttrs, DumpEnumAsStr, CorrError
from corrscope.layout import LayoutConfig
from corrscope.renderer import MatplotlibRenderer, RendererConfig
from corrscope.triggers import ITriggerConfig, CorrelationTriggerConfig, PerFrameCache
from corrscope.renderer import MatplotlibRenderer, RendererConfig, Renderer
from corrscope.triggers import (
ITriggerConfig,
CorrelationTriggerConfig,
PerFrameCache,
CorrelationTrigger,
)
from corrscope.util import pushd, coalesce
from corrscope.wave import Wave, Flatten
if TYPE_CHECKING:
from corrscope.triggers import CorrelationTrigger
PRINT_TIMESTAMP = True
@register_enum
# Placing Enum before any other superclass results in errors.
# Placing DumpEnumAsStr before IntEnum or (int, Enum) results in errors on Python 3.6:
# - TypeError: object.__new__(BenchmarkMode) is not safe, use int.__new__()
# I don't know *why* this works. It's magic.
@unique
class BenchmarkMode(IntEnum):
class BenchmarkMode(int, DumpEnumAsStr, Enum):
NONE = 0
TRIGGER = 1
RENDER = 2
@ -89,7 +95,7 @@ class Config(
show_internals: List[str] = attr.Factory(list)
benchmark_mode: Union[str, BenchmarkMode] = BenchmarkMode.NONE
def __attrs_post_init__(self):
def __attrs_post_init__(self) -> None:
# Cast benchmark_mode to enum.
try:
if not isinstance(self.benchmark_mode, BenchmarkMode):
@ -186,7 +192,7 @@ class CorrScope:
outputs: List[outputs_.Output]
nchan: int
def _load_channels(self):
def _load_channels(self) -> None:
with pushd(self.arg.cfg_dir):
# Tell user if master audio path is invalid.
# (Otherwise, only ffmpeg uses the value of master_audio)
@ -202,7 +208,7 @@ class CorrScope:
self.nchan = len(self.channels)
@contextmanager
def _load_outputs(self):
def _load_outputs(self) -> Iterator[None]:
with pushd(self.arg.cfg_dir):
with ExitStack() as stack:
self.outputs = [
@ -211,13 +217,13 @@ class CorrScope:
]
yield
def _load_renderer(self):
def _load_renderer(self) -> Renderer:
renderer = MatplotlibRenderer(
self.cfg.render, self.cfg.layout, self.nchan, self.cfg.channels
)
return renderer
def play(self):
def play(self) -> None:
if self.has_played:
raise ValueError("Cannot call CorrScope.play() more than once")
self.has_played = True
@ -270,7 +276,7 @@ class CorrScope:
if PRINT_TIMESTAMP:
begin = time.perf_counter()
benchmark_mode = self.cfg.benchmark_mode
benchmark_mode = cast(BenchmarkMode, self.cfg.benchmark_mode)
not_benchmarking = not benchmark_mode
with self._load_outputs():
@ -327,13 +333,13 @@ class CorrScope:
# region Display buffers, for debugging purposes.
if extra_outputs.window:
triggers: List["CorrelationTrigger"] = self.triggers
triggers = cast(List[CorrelationTrigger], self.triggers)
extra_outputs.window.render_frame(
[trigger._prev_window for trigger in triggers]
)
if extra_outputs.buffer:
triggers: List["CorrelationTrigger"] = self.triggers
triggers = cast(List[CorrelationTrigger], self.triggers)
extra_outputs.buffer.render_frame(
[trigger._buffer for trigger in triggers]
)

Wyświetl plik

@ -3,13 +3,14 @@ import sys
import traceback
from pathlib import Path
from types import MethodType
from typing import Type, Optional, List, Any, Tuple, Callable, Union, Dict
from typing import Optional, List, Any, Tuple, Callable, Union, Dict, Sequence
import PyQt5.QtCore as qc
import PyQt5.QtWidgets as qw
import attr
from PyQt5 import uic
from PyQt5.QtCore import QModelIndex, Qt
from PyQt5.QtCore import QVariant
from PyQt5.QtGui import QKeySequence, QFont, QCloseEvent
from PyQt5.QtWidgets import QShortcut
@ -216,7 +217,7 @@ class MainWindow(qw.QMainWindow):
qw.QMessageBox.critical(self, "Error loading file", str(e))
return
def load_cfg(self, cfg: Config, cfg_path: Optional[Path]):
def load_cfg(self, cfg: Config, cfg_path: Optional[Path]) -> None:
self._cfg_path = cfg_path
self._any_unsaved = False
self.load_title()
@ -242,11 +243,11 @@ class MainWindow(qw.QMainWindow):
title_cache: str
def load_title(self):
def load_title(self) -> None:
self.title_cache = self.title
self._update_unsaved_title()
def _update_unsaved_title(self):
def _update_unsaved_title(self) -> None:
if self.any_unsaved:
undo_str = "*"
else:
@ -496,7 +497,9 @@ class CorrProgressDialog(qw.QProgressDialog):
# *arg_types: type
def run_on_ui_thread(bound_slot: MethodType, types: Tuple[type, ...]) -> Callable:
def run_on_ui_thread(
bound_slot: MethodType, types: Tuple[type, ...]
) -> Callable[..., None]:
""" Runs an object's slot on the object's own thread.
It's terrible code but it works (as long as the slot has no return value).
"""
@ -565,7 +568,7 @@ def nrow_ncol_property(altered: str, unaltered: str) -> property:
return property(get, set)
def default_property(path: str, default) -> property:
def default_property(path: str, default: Any) -> property:
def getter(self: "ConfigModel"):
val = rgetattr(self.cfg, path)
if val is None:
@ -600,6 +603,7 @@ def color2hex_maybe_property(path: str) -> property:
return color2hex(color_attr)
def setter(self: "ConfigModel", val: str):
color: Optional[str]
if val:
color = color2hex(val)
else:
@ -633,7 +637,7 @@ flatten_modes = {
Flatten.DiffAvg: "DiffAvg: (L-R)/2",
Flatten.Stereo: "Stereo (broken)",
}
assert set(flatten_modes.keys()) == set(Flatten.modes)
assert set(flatten_modes.keys()) == set(Flatten.modes) # type: ignore
flatten_symbols = list(flatten_modes.keys())
flatten_text = list(flatten_modes.values())
@ -641,8 +645,8 @@ flatten_text = list(flatten_modes.values())
class ConfigModel(PresentationModel):
cfg: Config
combo_symbols: Dict[str, List[Symbol]] = {}
combo_text: Dict[str, List[str]] = {}
combo_symbols: Dict[str, Sequence[Symbol]] = {}
combo_text: Dict[str, Sequence[str]] = {}
master_audio = path_fix_property("master_audio")
@ -751,7 +755,7 @@ class ChannelTableView(qw.QTableView):
@attr.dataclass
class Column:
key: str
cls: Union[Type, Callable]
cls: Union[type, Callable[[str], Any]]
default: Any
def _display_name(self) -> str:
@ -787,7 +791,7 @@ class ChannelModel(qc.QAbstractTableModel):
cfg.trigger = trigger_dict
def triggers(self, row: int) -> dict:
def triggers(self, row: int) -> Dict[str, Any]:
trigger = self.channels[row].trigger
assert isinstance(trigger, dict)
return trigger
@ -803,18 +807,17 @@ class ChannelModel(qc.QAbstractTableModel):
Column("trigger__buffer_falloff", float, None),
]
@staticmethod
def _idx_of_key(col_data=col_data):
return {col.key: idx for idx, col in enumerate(col_data)}
idx_of_key = _idx_of_key.__func__()
idx_of_key = {}
for idx, col in enumerate(col_data):
idx_of_key[col.key] = idx
del idx, col
def columnCount(self, parent: QModelIndex = ...) -> int:
return len(self.col_data)
def headerData(
self, section: int, orientation: Qt.Orientation, role=Qt.DisplayRole
):
self, section: int, orientation: Qt.Orientation, role: int = Qt.DisplayRole
) -> Union[str, QVariant]:
if role == Qt.DisplayRole:
if orientation == Qt.Horizontal:
col = section

Wyświetl plik

@ -1,6 +1,16 @@
import functools
import operator
from typing import Optional, List, Callable, Dict, Any, ClassVar, TYPE_CHECKING, Union
from typing import (
Optional,
List,
Callable,
Dict,
Any,
ClassVar,
TYPE_CHECKING,
Union,
Sequence,
)
from PyQt5 import QtWidgets as qw, QtCore as qc
from PyQt5.QtCore import pyqtSlot
@ -36,8 +46,8 @@ class PresentationModel(qc.QObject):
# These fields are specific to each subclass, and assigned there.
# Although less explicit, these can be assigned using __init_subclass__.
combo_symbols: Dict[str, List[Symbol]]
combo_text: Dict[str, List[str]]
combo_symbols: Dict[str, Sequence[Symbol]]
combo_text: Dict[str, Sequence[str]]
edited = qc.pyqtSignal()
def __init__(self, cfg: DumpableAttrs):
@ -45,7 +55,7 @@ class PresentationModel(qc.QObject):
self.cfg = cfg
self.update_widget: Dict[str, WidgetUpdater] = {}
def __getitem__(self, item):
def __getitem__(self, item: str) -> Any:
try:
# Custom properties
return getattr(self, item)
@ -70,7 +80,7 @@ class PresentationModel(qc.QObject):
# TODO add tests for recursive operations
def map_gui(view: "MainWindow", model: PresentationModel):
def map_gui(view: "MainWindow", model: PresentationModel) -> None:
"""
Binding:
- .ui <widget name="layout__nrows">
@ -133,7 +143,7 @@ class BoundWidget(QWidget):
# PresentationModel+set_model vs. cfg2gui+set_gui vs. widget
# Feel free to improve the naming.
def cfg2gui(self):
def cfg2gui(self) -> None:
""" Update the widget without triggering signals.
When the presentation pmodel updates dependent widget 1,
@ -153,7 +163,9 @@ class BoundWidget(QWidget):
pass
def blend_colors(color1: QColor, color2: QColor, ratio: float, gamma=2):
def blend_colors(
color1: QColor, color2: QColor, ratio: float, gamma: float = 2
) -> QColor:
""" Blends two colors in linear color space.
Produces better results on both light and dark themes,
than integer blending (which is too dark).
@ -169,7 +181,7 @@ def blend_colors(color1: QColor, color2: QColor, ratio: float, gamma=2):
return QColor.fromRgbF(*rgb_blend, 1.0)
def model_setter(value_type: type) -> Callable:
def model_setter(value_type: type) -> Callable[[Any], None]:
@pyqtSlot(value_type)
def set_model(self: BoundWidget, value):
assert isinstance(value, value_type)
@ -183,7 +195,7 @@ def model_setter(value_type: type) -> Callable:
return set_model
def alias(name: str):
def alias(name: str) -> property:
return property(operator.attrgetter(name))
@ -208,7 +220,7 @@ class BoundDoubleSpinBox(qw.QDoubleSpinBox, BoundWidget):
class BoundComboBox(qw.QComboBox, BoundWidget):
combo_symbols: List[Symbol]
combo_symbols: Sequence[Symbol]
symbol2idx: Dict[Symbol, int]
# noinspection PyAttributeOutsideInit
@ -228,7 +240,7 @@ class BoundComboBox(qw.QComboBox, BoundWidget):
BoundWidget.bind_widget(self, model, path)
# combobox.index = pmodel.attr
def set_gui(self, symbol: Symbol):
def set_gui(self, symbol: Symbol) -> None:
combo_index = self.symbol2idx[symbol]
self.setCurrentIndex(combo_index)
@ -257,7 +269,7 @@ def behead(string: str, header: str) -> str:
DUNDER = "__"
# https://gist.github.com/wonderbeyond/d293e7a2af1de4873f2d757edd580288
def rgetattr(obj, dunder_delim_path: str, *default):
def rgetattr(obj: DumpableAttrs, dunder_delim_path: str, *default) -> Any:
"""
:param obj: Object
:param dunder_delim_path: 'attr1__attr2__etc'
@ -268,7 +280,7 @@ def rgetattr(obj, dunder_delim_path: str, *default):
def _getattr(obj, attr):
return getattr(obj, attr, *default)
attrs = dunder_delim_path.split(DUNDER)
attrs: List[Any] = dunder_delim_path.split(DUNDER)
return functools.reduce(_getattr, [obj] + attrs)

Wyświetl plik

@ -19,7 +19,7 @@ class FileName:
def _get_hist_name(
func: Callable,
func: Callable[..., Tuple[str, str]],
history_dir: _gp.Ref[_gp.GlobalPrefs],
parent: qw.QWidget,
title: str,
@ -37,7 +37,7 @@ def _get_hist_name(
filter: str = ";;".join(filters)
# Call qw.QFileDialog.getXFileName[s].
name, sel_filter = func(parent, title, dir_or_file, filter) # type: str, str
name, sel_filter = func(parent, title, dir_or_file, filter)
if not name:
return None

Wyświetl plik

@ -1,15 +1,15 @@
import html
from typing import TypeVar, Iterable, Generic, Tuple
from typing import TypeVar, Iterable, Generic, Tuple, Any, Optional
import matplotlib.colors
import more_itertools
from PyQt5.QtCore import QMutex
from PyQt5.QtWidgets import QErrorMessage
from PyQt5.QtWidgets import QErrorMessage, QWidget
from corrscope.config import CorrError
def color2hex(color):
def color2hex(color: Any) -> str:
try:
return matplotlib.colors.to_hex(color, keep_alpha=False)
except ValueError:
@ -33,7 +33,7 @@ class Locked(Generic[T]):
self.lock.lock()
return self.obj
def __exit__(self, *args, **kwargs):
def __exit__(self, *args) -> None:
self.lock.unlock()
def set(self, value: T) -> T:
@ -57,11 +57,11 @@ class TracebackDialog(QErrorMessage):
</style>
<body>%s</body>"""
def __init__(self, parent=None):
def __init__(self, parent: Optional[QWidget] = None):
QErrorMessage.__init__(self, parent)
self.resize(self.w, self.h)
def showMessage(self, message, type=None):
def showMessage(self, message: str, type: Any = None) -> None:
message = self.template % (html.escape(message))
QErrorMessage.showMessage(self, message, type)

Wyświetl plik

@ -1,4 +1,4 @@
from typing import Optional, TypeVar, Callable, List, Generic
from typing import Optional, TypeVar, Callable, List, Generic, Tuple
import numpy as np
@ -11,7 +11,7 @@ class LayoutConfig(DumpableAttrs, always_dump="orientation"):
nrows: Optional[int] = None
ncols: Optional[int] = None
def __attrs_post_init__(self):
def __attrs_post_init__(self) -> None:
if not self.nrows:
self.nrows = None
if not self.ncols:
@ -45,7 +45,7 @@ class RendererLayout:
f"{self.VALID_ORIENTATIONS}"
)
def _calc_layout(self):
def _calc_layout(self) -> Tuple[int, int]:
"""
Inputs: self.cfg, self.waves
:return: (nrows, ncols)
@ -58,17 +58,17 @@ class RendererLayout:
raise ValueError("impossible cfg: nrows is None and true")
ncols = ceildiv(self.nplots, nrows)
else:
ncols = cfg.ncols
if ncols is None:
if cfg.ncols is None:
raise ValueError(
"invalid LayoutConfig: nrows,ncols is None "
"(__attrs_post_init__ not called?)"
)
ncols = cfg.ncols
nrows = ceildiv(self.nplots, ncols)
return nrows, ncols
def arrange(self, region_factory: RegionFactory) -> List[Region]:
def arrange(self, region_factory: RegionFactory[Region]) -> List[Region]:
""" Generates an array of regions.
index, row, column are fed into region_factory in a row-major order [row][col].

Wyświetl plik

@ -3,7 +3,7 @@ import shlex
import subprocess
from abc import ABC, abstractmethod
from os.path import abspath
from typing import TYPE_CHECKING, Type, List, Union, Optional, ClassVar
from typing import TYPE_CHECKING, Type, List, Union, Optional, ClassVar, Callable
import numpy as np
@ -26,7 +26,7 @@ FFMPEG_QUIET = "-nostats -hide_banner -loglevel error".split()
class IOutputConfig(DumpableAttrs):
cls: "ClassVar[Type[Output]]"
def __call__(self, corr_cfg: "Config"):
def __call__(self, corr_cfg: "Config") -> "Output":
return self.cls(corr_cfg, cfg=self)
@ -64,7 +64,9 @@ class Output(ABC):
# Glue logic
def register_output(config_t: Type[IOutputConfig]):
def register_output(
config_t: Type[IOutputConfig]
) -> Callable[[Type[Output]], Type[Output]]:
def inner(output_t: Type[Output]):
config_t.cls = output_t
return output_t
@ -98,7 +100,7 @@ class _FFmpegProcess:
if self.corr_cfg.master_audio:
self.templates.append(cfg.audio_template) # audio
def popen(self, extra_args, bufsize, **kwargs) -> subprocess.Popen:
def popen(self, extra_args: List[str], bufsize: int, **kwargs) -> subprocess.Popen:
"""Raises FileNotFoundError if FFmpeg missing"""
try:
args = self._generate_args() + extra_args
@ -130,7 +132,7 @@ def ffmpeg_input_audio(audio_path: str) -> List[str]:
class PipeOutput(Output):
def open(self, *pipeline: subprocess.Popen):
def open(self, *pipeline: subprocess.Popen) -> None:
""" Called by __init__ with a Popen pipeline to ffmpeg/ffplay. """
if len(pipeline) == 0:
raise TypeError("must provide at least one Popen argument to popens")
@ -140,7 +142,7 @@ class PipeOutput(Output):
# Python documentation discourages accessing popen.stdin. It's wrong.
# https://stackoverflow.com/a/9886747
def __enter__(self):
def __enter__(self) -> Output:
return self
def write_frame(self, frame: ByteBuffer) -> Optional[_Stop]:
@ -161,7 +163,7 @@ class PipeOutput(Output):
else:
raise
def close(self, wait=True) -> int:
def close(self, wait: bool = True) -> int:
try:
self._stream.close()
# technically it should match the above exception handler,
@ -183,7 +185,7 @@ class PipeOutput(Output):
else:
self.terminate()
def terminate(self):
def terminate(self) -> None:
# Calling self.close() is bad.
# If exception occurred but ffplay continues running,
# popen.wait() will prevent stack trace from showing up.

Wyświetl plik

@ -41,7 +41,7 @@ if TYPE_CHECKING:
from corrscope.channel import ChannelConfig
def default_color():
def default_color() -> str:
# import matplotlib.colors
# colors = np.array([int(x, 16) for x in '1f 77 b4'.split()], dtype=float)
# colors /= np.amax(colors)
@ -63,7 +63,7 @@ class RendererConfig(DumpableAttrs, always_dump="*"):
# Performance (skipped when recording to video)
res_divisor: float = 1.0
def __attrs_post_init__(self):
def __attrs_post_init__(self) -> None:
# round(np.int32 / float) == np.float32, but we want int.
assert isinstance(self.width, (int, float))
assert isinstance(self.height, (int, float))

Wyświetl plik

@ -33,7 +33,7 @@ class GlobalPrefs(DumpableAttrs, always_dump="*"):
file_dir: str = ""
@property
def file_dir_ref(self) -> Ref:
def file_dir_ref(self) -> "Ref[GlobalPrefs]":
return Ref(self, "file_dir")
# Most recent video rendered
@ -41,7 +41,7 @@ class GlobalPrefs(DumpableAttrs, always_dump="*"):
render_dir: str = "" # Set to "" whenever separate_render_dir=False.
@property
def render_dir_ref(self) -> Ref:
def render_dir_ref(self) -> "Ref[GlobalPrefs]":
if self.separate_render_dir:
return Ref(self, "render_dir")
else:

Wyświetl plik

@ -1,7 +1,7 @@
import os
import platform
import sys
from typing import Dict, List
from typing import MutableMapping, List
from pathlib import Path
from appdirs import user_data_dir
@ -13,7 +13,7 @@ from corrscope.config import CorrError
__all__ = ["appdata_dir", "PATH_dir", "get_ffmpeg_url", "MissingFFmpegError"]
def prepend(dic: Dict[str, str], _key: List[str], prefix: str) -> None:
def prepend(dic: MutableMapping[str, str], _key: List[str], prefix: str) -> None:
""" Dubiously readable syntactic sugar for prepending to a string in a dict. """
key = _key[0]
dic[key] = prefix + dic[key]
@ -36,7 +36,7 @@ def get_ffmpeg_url() -> str:
# is_python_64 = sys.maxsize > 2 ** 32
is_os_64 = platform.machine().endswith("64")
def url(os_ver):
def url(os_ver: str) -> str:
return f"https://ffmpeg.zeranoe.com/builds/{os_ver}/shared/ffmpeg-latest-{os_ver}-shared.zip"
if sys.platform == "win32" and is_os_64:
@ -60,5 +60,5 @@ class MissingFFmpegError(CorrError):
else:
message += "Cannot download FFmpeg for your platform."
def __str__(self):
def __str__(self) -> str:
return self.message

Wyświetl plik

@ -1,6 +1,6 @@
import warnings
from abc import ABC, abstractmethod
from typing import TYPE_CHECKING, Type, Tuple, Optional, ClassVar
from typing import TYPE_CHECKING, Type, Tuple, Optional, ClassVar, Callable, Union
import attr
import numpy as np
@ -28,7 +28,9 @@ class ITriggerConfig(KeywordAttrs):
return self.cls(wave, cfg=self, tsamp=tsamp, stride=stride, fps=fps)
def register_trigger(config_t: Type[ITriggerConfig]):
def register_trigger(
config_t: Type[ITriggerConfig]
) -> "Callable[[Type[Trigger]], Type[Trigger]]": # my god mypy-strict sucks
""" @register_trigger(FooTriggerConfig)
def FooTrigger(): ...
"""
@ -69,7 +71,7 @@ class Trigger(ABC):
else:
self.post = None
def time2tsamp(self, time: float):
def time2tsamp(self, time: float) -> int:
return round(time * self._wave.smp_s / self._stride)
@abstractmethod
@ -122,7 +124,7 @@ class CorrelationTriggerConfig(ITriggerConfig):
use_edge_trigger: bool
# endregion
def __attrs_post_init__(self):
def __attrs_post_init__(self) -> None:
self._validate_param("lag_prevention", 0, 1)
self._validate_param("responsiveness", 0, 1)
# TODO trigger_falloff >= 0
@ -138,7 +140,7 @@ class CorrelationTriggerConfig(ITriggerConfig):
else:
self.post = ZeroCrossingTriggerConfig()
def _validate_param(self, key: str, begin, end):
def _validate_param(self, key: str, begin: float, end: float) -> None:
value = getattr(self, key)
if not begin <= value <= end:
raise CorrError(
@ -174,10 +176,10 @@ class CorrelationTrigger(Trigger):
self._windowed_step = self._calc_step()
# Will be overwritten on the first frame.
self._prev_period = None
self._prev_window = None
self._prev_period: Optional[int] = None
self._prev_window: Optional[np.ndarray] = None
def _calc_data_taper(self):
def _calc_data_taper(self) -> np.ndarray:
""" Input data window. Zeroes out all data older than 1 frame old.
See https://github.com/nyanpasu64/corrscope/wiki/Correlation-Trigger
"""
@ -215,7 +217,7 @@ class CorrelationTrigger(Trigger):
return data_taper
def _calc_step(self):
def _calc_step(self) -> np.ndarray:
""" Step function used for approximate edge triggering. """
# Increasing buffer_falloff (width of history buffer)
@ -304,7 +306,7 @@ class CorrelationTrigger(Trigger):
return trigger
def _is_window_invalid(self, period):
def _is_window_invalid(self, period: int) -> bool:
""" Returns True if pitch has changed more than `recalc_semitones`. """
prev = self._prev_period
@ -354,7 +356,7 @@ class CorrelationTrigger(Trigger):
# get_trigger()
def calc_step(nsamp: int, peak: float, stdev: float):
def calc_step(nsamp: int, peak: float, stdev: float) -> np.ndarray:
""" Step function used for approximate edge triggering.
TODO deduplicate CorrelationTrigger._calc_step() """
N = nsamp
@ -387,7 +389,7 @@ def get_period(data: np.ndarray) -> int:
return int(peakX)
def cosine_flat(n: int, diameter: int, falloff: int):
def cosine_flat(n: int, diameter: int, falloff: int) -> np.ndarray:
cosine = windows.hann(falloff * 2)
left, right = cosine[:falloff], cosine[falloff:]
@ -410,7 +412,7 @@ def normalize_buffer(data: np.ndarray) -> None:
data /= max(peak, MIN_AMPLITUDE)
def lerp(x: np.ndarray, y: np.ndarray, a: float):
def lerp(x: np.ndarray, y: np.ndarray, a: float) -> Union[np.ndarray, float]:
return x * (1 - a) + y * a
@ -530,7 +532,7 @@ class ZeroCrossingTrigger(PostTrigger):
# ZeroCrossingTrigger is only used as a postprocessing trigger.
# stride is only passed 1, for improved precision.
def get_trigger(self, index: int, cache: "PerFrameCache"):
def get_trigger(self, index: int, cache: "PerFrameCache") -> int:
# 'cache' is unused.
tsamp = self._tsamp

Wyświetl plik

@ -3,12 +3,12 @@ import sys
from contextlib import contextmanager
from itertools import chain
from pathlib import Path
from typing import Callable, Tuple, TypeVar, Iterator, Union, Optional
from typing import Callable, Tuple, TypeVar, Iterator, Union, Optional, Any, cast
import numpy as np
def ceildiv(n, d):
def ceildiv(n: int, d: int) -> int:
return -(-n // d)
@ -24,16 +24,16 @@ def coalesce(*args: Optional[T]) -> T:
raise TypeError("coalesce() called with all None")
def obj_name(obj) -> str:
def obj_name(obj: Any) -> str:
return type(obj).__name__
# Adapted from https://github.com/numpy/numpy/issues/2269#issuecomment-14436725
def find(
a: "np.ndarray[T]",
predicate: "Callable[[np.ndarray[T]], np.ndarray[bool]]",
chunk_size=1024,
) -> Iterator[Tuple[Tuple[int], T]]:
a: "np.ndarray[float]",
predicate: "Callable[[np.ndarray], np.ndarray[bool]]",
chunk_size: int = 1024,
) -> Iterator[Tuple[Tuple[int], float]]:
"""
Find the indices of array elements that match the predicate.
@ -88,11 +88,11 @@ def find(
chunk = a[i0:i1]
for idx in predicate(chunk).nonzero()[0]:
yield (idx + i0,), chunk[idx]
i0 = i1
i0 = cast(int, i1)
@contextmanager
def pushd(new_dir: Union[Path, str]):
def pushd(new_dir: Union[Path, str]) -> Iterator[None]:
previous_dir = os.getcwd()
os.chdir(str(new_dir))
try:
@ -101,5 +101,5 @@ def pushd(new_dir: Union[Path, str]):
os.chdir(previous_dir)
def perr(*args, **kwargs):
def perr(*args, **kwargs) -> None:
print(*args, file=sys.stderr, **kwargs)

Wyświetl plik

@ -3,7 +3,7 @@ from bisect import bisect_left
import numpy as np
def correlate(in1, in2) -> np.ndarray:
def correlate(in1: np.ndarray, in2: np.ndarray) -> np.ndarray:
"""
Based on scipy.correlate.
Assumed: mode='full', method='fft'
@ -30,11 +30,11 @@ def correlate(in1, in2) -> np.ndarray:
return ret
def _reverse_and_conj(x):
def _reverse_and_conj(x: np.ndarray) -> np.ndarray:
return x[::-1].conj()
def next_fast_len(target):
def next_fast_len(target: int) -> int:
"""
Find the next fast size of input data to `fft`, for zero-padding, etc.

Wyświetl plik

@ -46,6 +46,9 @@ import numpy
import struct
import warnings
from typing import Tuple, TYPE_CHECKING
if TYPE_CHECKING:
from io import BufferedReader
__all__ = [
'WavFileWarning',
@ -67,7 +70,9 @@ KNOWN_WAVE_FORMATS = (WAVE_FORMAT_PCM, WAVE_FORMAT_IEEE_FLOAT)
# after the 'fmt ' id
def _read_fmt_chunk(fid, is_big_endian):
def _read_fmt_chunk(
fid: "BufferedReader", is_big_endian: bool
) -> Tuple[int, int, int, int, int, int, int]:
"""
Returns
-------
@ -133,8 +138,10 @@ def _read_fmt_chunk(fid, is_big_endian):
# assumes file pointer is immediately after the 'data' id
def _read_data_chunk(fid, format_tag, channels, bit_depth, is_big_endian,
mmap=False):
def _read_data_chunk(
fid: "BufferedReader", format_tag: int, channels: int,
bit_depth: int, is_big_endian: bool, mmap: bool = False
) -> numpy.ndarray:
if is_big_endian:
fmt = '>I'
else:
@ -169,7 +176,7 @@ def _read_data_chunk(fid, format_tag, channels, bit_depth, is_big_endian,
return data
def _skip_unknown_chunk(fid, is_big_endian):
def _skip_unknown_chunk(fid: "BufferedReader", is_big_endian: bool) -> None:
if is_big_endian:
fmt = '>I'
else:
@ -185,7 +192,7 @@ def _skip_unknown_chunk(fid, is_big_endian):
fid.seek(size, 1)
def _read_riff_chunk(fid):
def _read_riff_chunk(fid: "BufferedReader") -> Tuple[int, bool]:
str1 = fid.read(4) # File signature
if str1 == b'RIFF':
is_big_endian = False
@ -208,7 +215,7 @@ def _read_riff_chunk(fid):
return file_size, is_big_endian
def read(filename, mmap=False):
def read(filename: str, mmap: bool = False) -> Tuple[int, numpy.ndarray]:
"""
Open a WAV file

Wyświetl plik

@ -33,8 +33,7 @@ THE POSSIBILITY OF SUCH DAMAGE.
from __future__ import division, print_function, absolute_import
import operator
import warnings
from typing import List, Tuple
import numpy as np
@ -45,14 +44,14 @@ __all__ = ['boxcar', 'triang', 'parzen', 'bohman', 'blackman', 'nuttall',
'exponential', 'tukey']
def _len_guards(M):
def _len_guards(M: int) -> bool:
"""Handle small or incorrect window lengths"""
if int(M) != M or M < 0:
raise ValueError('Window length M must be a non-negative integer')
return M <= 1
def _extend(M, sym):
def _extend(M: int, sym: bool) -> Tuple[int, bool]:
"""Extend window by 1 sample if needed for DFT-even symmetry"""
if not sym:
return M + 1, True
@ -60,7 +59,7 @@ def _extend(M, sym):
return M, False
def _truncate(w, needed):
def _truncate(w: np.ndarray, needed: bool) -> np.ndarray:
"""Truncate window by 1 sample if needed for DFT-even symmetry"""
if needed:
return w[:-1]
@ -68,7 +67,7 @@ def _truncate(w, needed):
return w
def general_cosine(M, a, sym=True):
def general_cosine(M: int, a: List[float], sym: bool = True) -> np.ndarray:
r"""
Generic weighted sum of cosine terms window
@ -734,7 +733,7 @@ def bartlett(M, sym=True):
return _truncate(w, needs_trunc)
def hann(M, sym=True):
def hann(M: int, sym: bool = True) -> np.ndarray:
r"""
Return a Hann window.
@ -958,7 +957,7 @@ def barthann(M, sym=True):
return _truncate(w, needs_trunc)
def general_hamming(M, alpha, sym=True):
def general_hamming(M: int, alpha: float, sym: bool = True) -> np.ndarray:
r"""Return a generalized Hamming window.
The generalized Hamming window is constructed by multiplying a rectangular
@ -1126,7 +1125,7 @@ def hamming(M, sym=True):
# def kaiser(M, beta, sym=True):
def gaussian(M, std, sym=True):
def gaussian(M: int, std: float, sym: bool = True) -> np.ndarray:
r"""Return a Gaussian window.
Parameters

Wyświetl plik

@ -1,7 +1,7 @@
import numpy as np
def leftpad(data: np.ndarray, n: int):
def leftpad(data: np.ndarray, n: int) -> np.ndarray:
if not n > 0:
raise ValueError(f"leftpad(n={n}) must be > 0")
@ -11,7 +11,7 @@ def leftpad(data: np.ndarray, n: int):
return data
def midpad(data: np.ndarray, n: int):
def midpad(data: np.ndarray, n: int) -> np.ndarray:
if not n > 0:
raise ValueError(f"midpad(n={n}) must be > 0")

Wyświetl plik

@ -49,6 +49,8 @@ class Wave:
data: "np.ndarray"
"""2-D array of shape (nsamp, nchan)"""
_flatten: Flatten
@property
def flatten(self) -> Flatten:
"""
@ -64,7 +66,7 @@ class Wave:
@flatten.setter
def flatten(self, flatten: Flatten) -> None:
# Reject invalid modes (including Mono).
if flatten not in Flatten.modes:
if flatten not in Flatten.modes: # type: ignore
# Flatten.Mono not in Flatten.modes.
raise CorrError(
f"Wave {self.wave_path} has invalid flatten mode {flatten} "
@ -161,7 +163,7 @@ class Wave:
region_len = end - begin
def constrain(idx):
def constrain(idx: int) -> int:
delta = 0
if idx < 0:
delta = 0 - idx # delta > 0

33
poetry.lock wygenerowano
Wyświetl plik

@ -177,6 +177,26 @@ version = "4.3.0"
[package.dependencies]
six = ">=1.0.0,<2.0.0"
[[package]]
category = "dev"
description = "Optional static typing for Python"
name = "mypy"
optional = false
python-versions = "*"
version = "0.660"
[package.dependencies]
mypy_extensions = ">=0.4.0,<0.5.0"
typed-ast = ">=1.2.0,<1.3.0"
[[package]]
category = "dev"
description = "Experimental type system extensions for programs checked with the mypy typechecker."
name = "mypy-extensions"
optional = false
python-versions = "*"
version = "0.4.1"
[[package]]
category = "main"
description = "NumPy: array processing for numbers, strings, records, and objects."
@ -341,6 +361,14 @@ optional = false
python-versions = ">=2.6, !=3.0.*, !=3.1.*"
version = "1.12.0"
[[package]]
category = "dev"
description = "a fork of Python 2 and 3 ast modules with type comment support"
name = "typed-ast"
optional = false
python-versions = "*"
version = "1.2.0"
[[package]]
category = "dev"
description = "HTTP library with thread-safe connection pooling, file post, and more."
@ -350,7 +378,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, <4"
version = "1.24.1"
[metadata]
content-hash = "5f805ebc68759139df36aae10a903fd9d347b2f23248eb32182c5b48b48e2a1c"
content-hash = "2776f62aa3fa7955bc6e7127743d24504efd279c21dfbdbc84ea45c71d5dbce8"
python-versions = "^3.6"
[metadata.hashes]
@ -373,6 +401,8 @@ kiwisolver = ["0ee4ed8b3ae8f5f712b0aa9ebd2858b5b232f1b9a96b0943dceb34df2a223bc3"
macholib = ["ac02d29898cf66f27510d8f39e9112ae00590adb4a48ec57b25028d6962b1ae1", "c4180ffc6f909bf8db6cd81cff4b6f601d575568f4d5dee148c830e9851eb9db"]
matplotlib = ["16aa61846efddf91df623bbb4598e63be1068a6b6a2e6361cc802b41c7a286eb", "1975b71a33ac986bb39b6d5cfbc15c7b1f218f1134efb4eb3881839d6ae69984", "2b222744bd54781e6cc0b717fa35a54e5f176ba2ced337f27c5b435b334ef854", "317643c0e88fad55414347216362b2e229c130edd5655fea5f8159a803098468", "4269ce3d1b897d46fc3cc2273a0cc2a730345bb47e4456af662e6fca85c89dd7", "65214fd668975077cdf8d408ccf2b2d6bdf73b4e6895a79f8e99ce4f0b43fcdb", "74bc213ab8a92d86a0b304d9359d1e1d14168d4c6121b83862c9d8a88b89a738", "88949be0db54755995dfb0210d0099a8712a3c696c860441971354c3debfc4af", "8e1223d868be89423ec95ada5f37aa408ee64fe76ccb8e4d5f533699ba4c0e4a", "9fa00f2d7a552a95fa6016e498fdeb6d74df537853dda79a9055c53dfc8b6e1a", "c27fd46cab905097ba4bc28d5ba5289930f313fb1970c9d41092c9975b80e9b4", "c94b792af431f6adb6859eb218137acd9a35f4f7442cea57e4a59c54751c36af", "f4c12a01eb2dc16693887a874ba948b18c92f425c4d329639ece6d3bb8e631bb"]
more-itertools = ["c187a73da93e7a8acc0001572aebc7e3c69daf7bf6881a2cea10650bd4420092", "c476b5d3a34e12d40130bc2f935028b5f636df8f372dc2c1c01dc19681b2039e", "fcbfeaea0be121980e15bc97b3817b5202ca73d0eae185b4550cbfce2a3ebb3d"]
mypy = ["986a7f97808a865405c5fd98fae5ebfa963c31520a56c783df159e9a81e41b3e", "cc5df73cc11d35655a8c364f45d07b13c8db82c000def4bd7721be13356533b4"]
mypy-extensions = ["37e0e956f41369209a3d5f34580150bcacfabaa57b33a15c0b25f4b5725e0812", "b16cabe759f55e3409a7d231ebd2841378fb0c27a5d1994719e340e4f429ac3e"]
numpy = ["0df89ca13c25eaa1621a3f09af4c8ba20da849692dcae184cb55e80952c453fb", "154c35f195fd3e1fad2569930ca51907057ae35e03938f89a8aedae91dd1b7c7", "18e84323cdb8de3325e741a7a8dd4a82db74fde363dce32b625324c7b32aa6d7", "1e8956c37fc138d65ded2d96ab3949bd49038cc6e8a4494b1515b0ba88c91565", "23557bdbca3ccbde3abaa12a6e82299bc92d2b9139011f8c16ca1bb8c75d1e95", "24fd645a5e5d224aa6e39d93e4a722fafa9160154f296fd5ef9580191c755053", "36e36b6868e4440760d4b9b44587ea1dc1f06532858d10abba98e851e154ca70", "3d734559db35aa3697dadcea492a423118c5c55d176da2f3be9c98d4803fc2a7", "416a2070acf3a2b5d586f9a6507bb97e33574df5bd7508ea970bbf4fc563fa52", "4a22dc3f5221a644dfe4a63bf990052cc674ef12a157b1056969079985c92816", "4d8d3e5aa6087490912c14a3c10fbdd380b40b421c13920ff468163bc50e016f", "4f41fd159fba1245e1958a99d349df49c616b133636e0cf668f169bce2aeac2d", "561ef098c50f91fbac2cc9305b68c915e9eb915a74d9038ecf8af274d748f76f", "56994e14b386b5c0a9b875a76d22d707b315fa037affc7819cda08b6d0489756", "73a1f2a529604c50c262179fcca59c87a05ff4614fe8a15c186934d84d09d9a5", "7da99445fd890206bfcc7419f79871ba8e73d9d9e6b82fe09980bc5bb4efc35f", "99d59e0bcadac4aa3280616591fb7bcd560e2218f5e31d5223a2e12a1425d495", "a4cc09489843c70b22e8373ca3dfa52b3fab778b57cf81462f1203b0852e95e3", "a61dc29cfca9831a03442a21d4b5fd77e3067beca4b5f81f1a89a04a71cf93fa", "b1853df739b32fa913cc59ad9137caa9cc3d97ff871e2bbd89c2a2a1d4a69451", "b1f44c335532c0581b77491b7715a871d0dd72e97487ac0f57337ccf3ab3469b", "b261e0cb0d6faa8fd6863af26d30351fd2ffdb15b82e51e81e96b9e9e2e7ba16", "c857ae5dba375ea26a6228f98c195fec0898a0fd91bcf0e8a0cae6d9faf3eca7", "cf5bb4a7d53a71bb6a0144d31df784a973b36d8687d615ef6a7e9b1809917a9b", "db9814ff0457b46f2e1d494c1efa4111ca089e08c8b983635ebffb9c1573361f", "df04f4bad8a359daa2ff74f8108ea051670cafbca533bb2636c58b16e962989e", "ecf81720934a0e18526177e645cbd6a8a21bb0ddc887ff9738de07a1df5c6b61", "edfa6fba9157e0e3be0f40168eb142511012683ac3dc82420bee4a3f3981b30e"]
pefile = ["4c5b7e2de0c8cb6c504592167acf83115cbbde01fe4a507c16a1422850e86cd6"]
pluggy = ["447ba94990e8014ee25ec853339faf7b0fc8050cdc3289d4d71f7f410fb90095", "bde19360a8ec4dfd8a20dcb811780a30998101f078fc7ded6162f0076f50508f"]
@ -389,4 +419,5 @@ pywin32-ctypes = ["24ffc3b341d457d48e8922352130cf2644024a4ff09762a2261fd34c36ee5
requests = ["502a824f31acdacb3a35b6690b5fbf0bc41d63a24a45c4004352b0242707598e", "7bf2a778576d825600030a110f3c0e3e8edc51dfaafe1c146e39a2027784957b"]
"ruamel.yaml" = ["046b997a0892eebd9ca97823102b330ab3a2da719a6df877b2f2a03e19fb878e", "0e7414b6b757ae64d5452a83fb197040a7835ff96fa228f7127c94d54041801d", "31d9a1c4d15c02d60c2ca1996dcaf1a6c91b986b72299384071cce40bd1278bf", "356ee57fa561243223d91cedb526fd057a91edb744825fe64b6fff23aa262324", "39ad6bad81d0c797452e5eeadf45da9c507c978894a5e4c3e8c6ce5ee8abe8db", "3dcede78252a4cf3820928fa75c5928a56cbaedd4c698ea2d1f04e6c36235d6a", "4329af449c314ea0565bb5c36dc52232ea9ca32297b58bb2340acf5767719ad6", "51d81999ae9cb138a8043908c9406b7bd258a4d616f99200627a81f1c9f85fcb", "53ea23234e82242267c4d32611e6907a64f0f2e03b77c19d270bd56b2e375f5b", "5a3bf78266761f352d61cb18ee91fa11a1970566238ec732e4ec8857bebdf91d", "5ec8f00fc23cca74dfc0529647bb16e5578e247c1ac5182be15dfb949d1304cb", "606d3f83ede7a3f76845de64bc8f376df40d166eca782a56a35b8d1961b214e1", "66af4e1a5f24534e9d63854ce028d5ccba642c387df6b1b9b39b7a0953d08135", "71baa4ca8a7e4e37991f28b17f75d64c56ef82163f79fc9d875712d04c531c5a", "7a32ab8866c04d844e1b7116118e7b8e38718a291802a60bb287964bcf4c0f47", "a29655dc08ddf64b22c462aad88e4f54b54f80fac2cd8edfe0dcf817987a2722", "c1de59975299c919058c54bb9309e430bdcea7239c76021cf082a841e6f8e43c", "c3af911ee65e406d7ab0655fc3f4124d5000fcadcb470fe07d37d975f77a1a4c", "cdd215fa38b15713378bccc99f3fd1eee06c328fe50b5b17775f7cf3bc047cac", "d0ad7a4f8cd8082d0593b87848a46c12fbfb5c5011fb15dfe58941aa2ee5e5e8", "e7e5fa36587e6e06b12a678314d2020f90928ea9522001ea7834e3f1e5ac3b98", "f34262929dfab42ce88e34e2bc525a36a5972563c5cac1582a20b575303039c4"]
six = ["3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c", "d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73"]
typed-ast = ["023625bfa9359e29bd6e24cac2a4503495b49761d48a5f1e38333fc4ac4d93fe", "07591f7a5fdff50e2e566c4c1e9df545c75d21e27d98d18cb405727ed0ef329c", "153e526b0f4ffbfada72d0bb5ffe8574ba02803d2f3a9c605c8cf99dfedd72a2", "3ad2bdcd46a4a1518d7376e9f5016d17718a9ed3c6a3f09203d832f6c165de4a", "3ea98c84df53ada97ee1c5159bb3bc784bd734231235a1ede14c8ae0775049f7", "51a7141ccd076fa561af107cfb7a8b6d06a008d92451a1ac7e73149d18e9a827", "52c93cd10e6c24e7ac97e8615da9f224fd75c61770515cb323316c30830ddb33", "6344c84baeda3d7b33e157f0b292e4dd53d05ddb57a63f738178c01cac4635c9", "64699ca1b3bd5070bdeb043e6d43bc1d0cebe08008548f4a6bee782b0ecce032", "74903f2e56bbffe29282ef8a5487d207d10be0f8513b41aff787d954a4cf91c9", "7891710dba83c29ee2bd51ecaa82f60f6bede40271af781110c08be134207bf2", "91976c56224e26c256a0de0f76d2004ab885a29423737684b4f7ebdd2f46dde2", "9bad678a576ecc71f25eba9f1e3fd8d01c28c12a2834850b458428b3e855f062", "b4726339a4c180a8b6ad9d8b50d2b6dc247e1b79b38fe2290549c98e82e4fd15", "ba36f6aa3f8933edf94ea35826daf92cbb3ec248b89eccdc053d4a815d285357", "bbc96bde544fd19e9ef168e4dfa5c3dfe704bfa78128fa76f361d64d6b0f731a", "c0c927f1e44469056f7f2dada266c79b577da378bbde3f6d2ada726d131e4824", "c0f9a3708008aa59f560fa1bd22385e05b79b8e38e0721a15a8402b089243442", "f0bf6f36ff9c5643004171f11d2fdc745aa3953c5aacf2536a0685db9ceb3fb1", "f5be39a0146be663cbf210a4d95c3c58b2d7df7b043c9047c5448e358f0550a2", "fcd198bf19d9213e5cbf2cde2b9ef20a9856e716f76f9476157f90ae6de06cc6"]
urllib3 = ["61bf29cada3fc2fbefad4fdf059ea4bd1b4a86d2b6d15e1c7c0b582b9752fe39", "de9529817c93f27c8ccbfead6985011db27bd0ddfcdb2d86f3f663385c6a9c22"]

Wyświetl plik

@ -27,6 +27,7 @@ pywin32-ctypes = {version = "^0.2.0",platform = "win32"}
coverage = "^4.5"
pytest-cov = "^2.6"
codecov = "^2.0"
mypy = "^0.660.0"
[tool.poetry.scripts]
corr = 'corrscope.cli:main'

Wyświetl plik

@ -50,3 +50,11 @@ def report():
def html():
run("coverage.cmdline:main", "html")
webbrowser.open("htmlcov/index.html")
"""
export MONKEYTYPE_TRACE_MODULES=corrscope
monkeytype run `which pytest`
// monkeytype run -m corrscope
monkeytype list-modules | xargs -I % -n 1 sh -c 'monkeytype apply % 2>&1 | tail -n4'
"""

Wyświetl plik

@ -1,7 +1,29 @@
[tool:pytest]
testpaths = tests
xfail_strict=true
[coverage:run]
branch = True
source =
corrscope
[mypy-corrscope.utils.scipy.*]
ignore_errors = True
[mypy]
;Pretty-print
show_error_context = True
;Config
ignore_missing_imports = True
;https://github.com/python/mypy/blob/master/mypy_self_check.ini
check_untyped_defs = True
warn_no_return = True
strict_optional = True
no_implicit_optional = True
disallow_any_generics = True
warn_redundant_casts = True
warn_unused_configs = True
show_traceback = True