From 480271285d285a56cc44c7573ff9f41f98f4e531 Mon Sep 17 00:00:00 2001 From: Tetsuo Koyama Date: Sun, 23 Aug 2020 13:21:39 +0900 Subject: [PATCH] =?UTF-8?q?=F0=9F=92=9A=20Add=20pylint=20in=20CI=20#39=20(?= =?UTF-8?q?#40)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add pylint in CI #39 * Improve rating to 8.87/10 * Fix codespell conf * Install dependencies * Setup python first * Use python 3.6 * Fix workflow * Restore workflow * Update .github/workflows/pythonpackage.yml * Fix C0103: invalid-name * Fix C0103 invalid-name * Add pylint Makefile target * Fix C0103 invalid-name * :recycle: fix by pylint * Fix Makefile * Move dragEnterEvent to function * Fix pylint error * Fix W0611 unused-import * Fix W0613: unused-argument * Fix isort * Revert of comment * Fix unnecessary-lambda * Fix unnecessary-lambda * Fix invalid name * Fix unused variable * Disable conflicting rule between black and pylint See : psf/black#48 PyCQA/pylint#289 * Fix reimported (W0404) * Fix C0412 (ungrouped-imports) * Fix missing-module-docstring * Fix too-many-arguments * Fix broad-except * Fix invalid-name * Fix disable to name * Fix too-many-instance-attributes * Fix pylint * Fix black * Fix invalid-name * Fix too-many-arguments * Update pyvistaqt/window.py * Update pyvistaqt/dialog.py * Fix pylint * Fix too-few-public-method * Fix too-few-public-methods * Remove enable F401 * Revert "Remove enable F401" This reverts commit c937bc5fc16cf56ba2e0eff37846fcb494df2e7c. * Fix attribute-defined-outside-init * Fix too-few-public-methods * Fix unsubscriptable-object * Fix unsubscriptable-object * Fix pycodestyle * Fix isort * Fix typo * Update pyvistaqt/plotting.py * Fix unnecessary-lambda * Update dialog.py * Update pyvistaqt/dialog.py * Update pyvistaqt/dialog.py * Update pyvistaqt/dialog.py * Update pyvistaqt/dialog.py * Fix invalid-name * Update pyvistaqt/dialog.py * Update pyvistaqt/dialog.py * Update pyvistaqt/dialog.py * Fix unused-argument * Fix at least two spaces before inline comment * Update pyvistaqt/plotting.py * Update pyvistaqt/plotting.py * Update pyvistaqt/plotting.py * Update pyvistaqt/plotting.py * Update pyvistaqt/plotting.py * Update pyvistaqt/plotting.py * Update pyvistaqt/plotting.py * Fix invalid-name * Update plotting.py * Update plotting.py * Fix no-self-use * Fix attribute-defined-outside-init * Fix attribute-defined-outside-init * Fix attribute-defined-outside-init * Fix E261 at least two spaces before inline comment * Update pyvistaqt/plotting.py * Update pyvistaqt/plotting.py * Update pyvistaqt/plotting.py * Update pyvistaqt/plotting.py * Update pyvistaqt/plotting.py * Update pyvistaqt/plotting.py * Update pyvistaqt/plotting.py * Fix attribute-defined-outside-init * Update pyvistaqt/window.py * Fix no cover * Update pyvistaqt/plotting.py * Update pyvistaqt/plotting.py * Fix pylint * Restore imports and adds docstrings * Deal with PyQt5 ungrouped-imports * Make isort * Add docstring Co-authored-by: Guillaume Favelier --- .github/workflows/pythonpackage.yml | 3 +- .pylintrc | 596 ++++++++++++++++++++++++++++ Makefile | 13 +- pyvistaqt/_version.py | 4 +- pyvistaqt/counter.py | 9 +- pyvistaqt/dialog.py | 29 +- pyvistaqt/plotting.py | 106 +++-- pyvistaqt/window.py | 12 +- 8 files changed, 711 insertions(+), 61 deletions(-) create mode 100644 .pylintrc diff --git a/.github/workflows/pythonpackage.yml b/.github/workflows/pythonpackage.yml index 6c60ba83..dc81135a 100644 --- a/.github/workflows/pythonpackage.yml +++ b/.github/workflows/pythonpackage.yml @@ -15,7 +15,8 @@ jobs: uses: ricardochaves/python-lint@v1.1.0 with: python-root-list: pyvistaqt - use-pylint: false + use-pylint: true + extra-pylint-options: --disable=F0401 use-pycodestyle: true use-flake8: true use-black: true diff --git a/.pylintrc b/.pylintrc new file mode 100644 index 00000000..91166a43 --- /dev/null +++ b/.pylintrc @@ -0,0 +1,596 @@ +[MASTER] + +# A comma-separated list of package or module names from where C extensions may +# be loaded. Extensions are loading into the active Python interpreter and may +# run arbitrary code. +extension-pkg-whitelist=PyQt5, vtk + +# Specify a score threshold to be exceeded before program exits with error. +fail-under=10 + +# Add files or directories to the blacklist. They should be base names, not +# paths. +ignore=CVS + +# Add files or directories matching the regex patterns to the blacklist. The +# regex matches against base names, not paths. +ignore-patterns= + +# Python code to execute, usually for sys.path manipulation such as +# pygtk.require(). +#init-hook= + +# Use multiple processes to speed up Pylint. Specifying 0 will auto-detect the +# number of processors available to use. +jobs=1 + +# Control the amount of potential inferred values when inferring a single +# object. This can help the performance when dealing with large functions or +# complex, nested conditions. +limit-inference-results=100 + +# List of plugins (as comma separated values of python module names) to load, +# usually to register additional checkers. +load-plugins= + +# Pickle collected data for later comparisons. +persistent=yes + +# When enabled, pylint would attempt to guess common misconfiguration and emit +# user-friendly hints instead of false-positive error messages. +suggestion-mode=yes + +# Allow loading of arbitrary C extensions. Extensions are imported into the +# active Python interpreter and may run arbitrary code. +unsafe-load-any-extension=no + + +[MESSAGES CONTROL] + +# Only show warnings with the listed confidence levels. Leave empty to show +# all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED. +confidence= + +# Disable the message, report, category or checker with the given id(s). You +# can either give multiple identifiers separated by comma (,) or put this +# option multiple times (only on the command line, not in the configuration +# file where it should appear only once). You can also use "--disable=all" to +# disable everything first and then re-enable specific checks. For example, if +# you want to run only the similarities checker, you can use "--disable=all +# --enable=similarities". If you want to run only the classes checker, but have +# no Warning level messages displayed, use "--disable=all --enable=classes +# --disable=W". +disable=print-statement, + parameter-unpacking, + unpacking-in-except, + old-raise-syntax, + backtick, + long-suffix, + old-ne-operator, + old-octal-literal, + import-star-module-level, + non-ascii-bytes-literal, + raw-checker-failed, + bad-inline-option, + locally-disabled, + file-ignored, + suppressed-message, + useless-suppression, + deprecated-pragma, + use-symbolic-message-instead, + apply-builtin, + basestring-builtin, + buffer-builtin, + cmp-builtin, + coerce-builtin, + execfile-builtin, + file-builtin, + long-builtin, + raw_input-builtin, + reduce-builtin, + standarderror-builtin, + unicode-builtin, + xrange-builtin, + coerce-method, + delslice-method, + getslice-method, + setslice-method, + no-absolute-import, + old-division, + dict-iter-method, + dict-view-method, + next-method-called, + metaclass-assignment, + indexing-exception, + raising-string, + reload-builtin, + oct-method, + hex-method, + nonzero-method, + cmp-method, + input-builtin, + round-builtin, + intern-builtin, + unichr-builtin, + map-builtin-not-iterating, + zip-builtin-not-iterating, + range-builtin-not-iterating, + filter-builtin-not-iterating, + using-cmp-argument, + eq-without-hash, + div-method, + idiv-method, + rdiv-method, + exception-message-attribute, + invalid-str-codec, + sys-max-int, + bad-python3-import, + deprecated-string-function, + deprecated-str-translate-call, + deprecated-itertools-function, + deprecated-types-field, + next-method-defined, + dict-items-not-iterating, + dict-keys-not-iterating, + dict-values-not-iterating, + deprecated-operator-function, + deprecated-urllib-function, + xreadlines-attribute, + deprecated-sys-function, + exception-escape, + comprehension-escape, + bad-continuation + +# Enable the message, report, category or checker with the given id(s). You can +# either give multiple identifier separated by comma (,) or put this option +# multiple time (only on the command line, not in the configuration file where +# it should appear only once). See also the "--disable" option for examples. +enable=c-extension-no-member + + +[REPORTS] + +# Python expression which should return a score less than or equal to 10. You +# have access to the variables 'error', 'warning', 'refactor', and 'convention' +# which contain the number of messages in each category, as well as 'statement' +# which is the total number of statements analyzed. This score is used by the +# global evaluation report (RP0004). +evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) + +# Template used to display messages. This is a python new-style format string +# used to format the message information. See doc for all details. +#msg-template= + +# Set the output format. Available formats are text, parseable, colorized, json +# and msvs (visual studio). You can also give a reporter class, e.g. +# mypackage.mymodule.MyReporterClass. +output-format=text + +# Tells whether to display a full report or only the messages. +reports=no + +# Activate the evaluation score. +score=yes + + +[REFACTORING] + +# Maximum number of nested blocks for function / method body +max-nested-blocks=5 + +# Complete name of functions that never returns. When checking for +# inconsistent-return-statements if a never returning function is called then +# it will be considered as an explicit return statement and no message will be +# printed. +never-returning-functions=sys.exit + + +[SPELLING] + +# Limits count of emitted suggestions for spelling mistakes. +max-spelling-suggestions=4 + +# Spelling dictionary name. Available dictionaries: none. To make it work, +# install the python-enchant package. +spelling-dict= + +# List of comma separated words that should not be checked. +spelling-ignore-words= + +# A path to a file that contains the private dictionary; one word per line. +spelling-private-dict-file= + +# Tells whether to store unknown words to the private dictionary (see the +# --spelling-private-dict-file option) instead of raising a message. +spelling-store-unknown-words=no + + +[BASIC] + +# Naming style matching correct argument names. +argument-naming-style=snake_case + +# Regular expression matching correct argument names. Overrides argument- +# naming-style. +#argument-rgx= + +# Naming style matching correct attribute names. +attr-naming-style=snake_case + +# Regular expression matching correct attribute names. Overrides attr-naming- +# style. +#attr-rgx= + +# Bad variable names which should always be refused, separated by a comma. +bad-names=foo, + bar, + baz, + toto, + tutu, + tata + +# Bad variable names regexes, separated by a comma. If names match any regex, +# they will always be refused +bad-names-rgxs= + +# Naming style matching correct class attribute names. +class-attribute-naming-style=any + +# Regular expression matching correct class attribute names. Overrides class- +# attribute-naming-style. +#class-attribute-rgx= + +# Naming style matching correct class names. +class-naming-style=PascalCase + +# Regular expression matching correct class names. Overrides class-naming- +# style. +#class-rgx= + +# Naming style matching correct constant names. +const-naming-style=UPPER_CASE + +# Regular expression matching correct constant names. Overrides const-naming- +# style. +#const-rgx= + +# Minimum line length for functions/classes that require docstrings, shorter +# ones are exempt. +docstring-min-length=-1 + +# Naming style matching correct function names. +function-naming-style=snake_case + +# Regular expression matching correct function names. Overrides function- +# naming-style. +#function-rgx= + +# Good variable names which should always be accepted, separated by a comma. +good-names=i, + j, + k, + ex, + Run, + _ + +# Good variable names regexes, separated by a comma. If names match any regex, +# they will always be accepted +good-names-rgxs= + +# Include a hint for the correct naming format with invalid-name. +include-naming-hint=no + +# Naming style matching correct inline iteration names. +inlinevar-naming-style=any + +# Regular expression matching correct inline iteration names. Overrides +# inlinevar-naming-style. +#inlinevar-rgx= + +# Naming style matching correct method names. +method-naming-style=snake_case + +# Regular expression matching correct method names. Overrides method-naming- +# style. +#method-rgx= + +# Naming style matching correct module names. +module-naming-style=snake_case + +# Regular expression matching correct module names. Overrides module-naming- +# style. +#module-rgx= + +# Colon-delimited sets of names that determine each other's naming style when +# the name regexes allow several styles. +name-group= + +# Regular expression which should only match function or class names that do +# not require a docstring. +no-docstring-rgx=^_ + +# List of decorators that produce properties, such as abc.abstractproperty. Add +# to this list to register other decorators that produce valid properties. +# These decorators are taken in consideration only for invalid-name. +property-classes=abc.abstractproperty + +# Naming style matching correct variable names. +variable-naming-style=snake_case + +# Regular expression matching correct variable names. Overrides variable- +# naming-style. +#variable-rgx= + + +[TYPECHECK] + +# List of decorators that produce context managers, such as +# contextlib.contextmanager. Add to this list to register other decorators that +# produce valid context managers. +contextmanager-decorators=contextlib.contextmanager + +# List of members which are set dynamically and missed by pylint inference +# system, and so shouldn't trigger E1101 when accessed. Python regular +# expressions are accepted. +generated-members= + +# Tells whether missing members accessed in mixin class should be ignored. A +# mixin class is detected if its name ends with "mixin" (case insensitive). +ignore-mixin-members=yes + +# Tells whether to warn about missing members when the owner of the attribute +# is inferred to be None. +ignore-none=yes + +# This flag controls whether pylint should warn about no-member and similar +# checks whenever an opaque object is returned when inferring. The inference +# can return multiple potential results while evaluating a Python object, but +# some branches might not be evaluated, which results in partial inference. In +# that case, it might be useful to still emit no-member and other checks for +# the rest of the inferred objects. +ignore-on-opaque-inference=yes + +# List of class names for which member attributes should not be checked (useful +# for classes with dynamically set attributes). This supports the use of +# qualified names. +ignored-classes=optparse.Values,thread._local,_thread._local + +# List of module names for which member attributes should not be checked +# (useful for modules/projects where namespaces are manipulated during runtime +# and thus existing member attributes cannot be deduced by static analysis). It +# supports qualified module names, as well as Unix pattern matching. +ignored-modules= + +# Show a hint with possible names when a member name was not found. The aspect +# of finding the hint is based on edit distance. +missing-member-hint=yes + +# The minimum edit distance a name should have in order to be considered a +# similar match for a missing member name. +missing-member-hint-distance=1 + +# The total number of similar names that should be taken in consideration when +# showing a hint for a missing member. +missing-member-max-choices=1 + +# List of decorators that change the signature of a decorated function. +signature-mutators= + + +[MISCELLANEOUS] + +# List of note tags to take in consideration, separated by a comma. +notes=FIXME, + XXX, + TODO + +# Regular expression of note tags to take in consideration. +#notes-rgx= + + +[VARIABLES] + +# List of additional names supposed to be defined in builtins. Remember that +# you should avoid defining new builtins when possible. +additional-builtins= + +# Tells whether unused global variables should be treated as a violation. +allow-global-unused-variables=yes + +# List of strings which can identify a callback function by name. A callback +# name must start or end with one of those strings. +callbacks=cb_, + _cb + +# A regular expression matching the name of dummy variables (i.e. expected to +# not be used). +dummy-variables-rgx=_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_ + +# Argument names that match this expression will be ignored. Default to name +# with leading underscore. +ignored-argument-names=_.*|^ignored_|^unused_ + +# Tells whether we should check for unused import in __init__ files. +init-import=no + +# List of qualified module names which can have objects that can redefine +# builtins. +redefining-builtins-modules=six.moves,past.builtins,future.builtins,builtins,io + + +[FORMAT] + +# Expected format of line ending, e.g. empty (any line ending), LF or CRLF. +expected-line-ending-format= + +# Regexp for a line that is allowed to be longer than the limit. +ignore-long-lines=^\s*(# )??$ + +# Number of spaces of indent required inside a hanging or continued line. +indent-after-paren=4 + +# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 +# tab). +indent-string=' ' + +# Maximum number of characters on a single line. +max-line-length=100 + +# Maximum number of lines in a module. +max-module-lines=1000 + +# List of optional constructs for which whitespace checking is disabled. `dict- +# separator` is used to allow tabulation in dicts, etc.: {1 : 1,\n222: 2}. +# `trailing-comma` allows a space between comma and closing bracket: (a, ). +# `empty-line` allows space-only lines. +no-space-check=trailing-comma, + dict-separator + +# Allow the body of a class to be on the same line as the declaration if body +# contains single statement. +single-line-class-stmt=no + +# Allow the body of an if to be on the same line as the test if there is no +# else. +single-line-if-stmt=no + + +[LOGGING] + +# The type of string formatting that logging methods do. `old` means using % +# formatting, `new` is for `{}` formatting. +logging-format-style=old + +# Logging modules to check that the string format arguments are in logging +# function parameter format. +logging-modules=logging + + +[STRING] + +# This flag controls whether inconsistent-quotes generates a warning when the +# character used as a quote delimiter is used inconsistently within a module. +check-quote-consistency=no + +# This flag controls whether the implicit-str-concat should generate a warning +# on implicit string concatenation in sequences defined over several lines. +check-str-concat-over-line-jumps=no + + +[SIMILARITIES] + +# Ignore comments when computing similarities. +ignore-comments=yes + +# Ignore docstrings when computing similarities. +ignore-docstrings=yes + +# Ignore imports when computing similarities. +ignore-imports=no + +# Minimum lines number of a similarity. +min-similarity-lines=4 + + +[CLASSES] + +# List of method names used to declare (i.e. assign) instance attributes. +defining-attr-methods=__init__, + __new__, + setUp, + __post_init__ + +# List of member names, which should be excluded from the protected access +# warning. +exclude-protected=_asdict, + _fields, + _replace, + _source, + _make + +# List of valid names for the first argument in a class method. +valid-classmethod-first-arg=cls + +# List of valid names for the first argument in a metaclass class method. +valid-metaclass-classmethod-first-arg=cls + + +[DESIGN] + +# Maximum number of arguments for function / method. +max-args=5 + +# Maximum number of attributes for a class (see R0902). +max-attributes=7 + +# Maximum number of boolean expressions in an if statement (see R0916). +max-bool-expr=5 + +# Maximum number of branch for function / method body. +max-branches=12 + +# Maximum number of locals for function / method body. +max-locals=15 + +# Maximum number of parents for a class (see R0901). +max-parents=7 + +# Maximum number of public methods for a class (see R0904). +max-public-methods=20 + +# Maximum number of return / yield for function / method body. +max-returns=6 + +# Maximum number of statements in function / method body. +max-statements=50 + +# Minimum number of public methods for a class (see R0903). +min-public-methods=2 + + +[IMPORTS] + +# List of modules that can be imported at any level, not just the top level +# one. +allow-any-import-level= + +# Allow wildcard imports from modules that define __all__. +allow-wildcard-with-all=no + +# Analyse import fallback blocks. This can be used to support both Python 2 and +# 3 compatible code, which means that the block might have code that exists +# only in one or another interpreter, leading to false positives when analysed. +analyse-fallback-blocks=no + +# Deprecated modules which should not be used, separated by a comma. +deprecated-modules=optparse,tkinter.tix + +# Create a graph of external dependencies in the given file (report RP0402 must +# not be disabled). +ext-import-graph= + +# Create a graph of every (i.e. internal and external) dependencies in the +# given file (report RP0402 must not be disabled). +import-graph= + +# Create a graph of internal dependencies in the given file (report RP0402 must +# not be disabled). +int-import-graph= + +# Force import order to recognize a module as part of the standard +# compatibility libraries. +known-standard-library= + +# Force import order to recognize a module as part of a third party library. +known-third-party=enchant + +# Couples of modules and preferred modules, separated by a comma. +preferred-modules= + + +[EXCEPTIONS] + +# Exceptions that will emit a warning when being caught. Defaults to +# "BaseException, Exception". +overgeneral-exceptions=BaseException, + Exception diff --git a/Makefile b/Makefile index da06ce7c..8ada487c 100644 --- a/Makefile +++ b/Makefile @@ -3,14 +3,17 @@ BLACK_DIRS ?= ./pyvistaqt/ ISORT_DIRS ?= ./pyvistaqt/*.py PYCODESTYLE_DIRS ?= ./pyvistaqt/ +PYLINT_DIRS ?= ./pyvistaqt/ FLAKE8_DIRS ?= ./pyvistaqt/ CODESPELL_DIRS ?= ./ CODESPELL_SKIP ?= "*.pyc,*.txt,*.gif,*.png,*.jpg,*.ply,*.vtk,*.vti,*.js,*.html,*.doctree,*.ttf,*.woff,*.woff2,*.eot,*.mp4,*.inv,*.pickle,*.ipynb,flycheck*,./.git/*,./.hypothesis/*,*.yml,./docs/_build/*,./docs/images/*,./dist/*,./.ci/*" CODESPELL_IGNORE ?= "ignore_words.txt" -EXTRA_PYCODESTYLE_OPTIONS ?= --ignore="E501,E203" -EXTRA_FLAKE8_OPTIONS ?= --ignore="E501,E203" +EXTRA_PYCODESTYLE_OPTIONS ?= --ignore="E501,E203,W503" +EXTRA_FLAKE8_OPTIONS ?= --ignore="E501,E203,W503" -all: doctest +all: srcstyle doctest + +srcstyle: black isort pylint pycodestyle flake8 doctest: codespell pydocstyle @@ -22,6 +25,10 @@ isort: @echo "Running isort" @isort $(ISORT_DIRS) +pylint: + @echo "Running pylint" + @pylint $(PYLINT_DIRS) + pycodestyle: @echo "Running pycodestyle" @pycodestyle $(PYCODESTYLE_DIRS) $(EXTRA_PYCODESTYLE_OPTIONS) diff --git a/pyvistaqt/_version.py b/pyvistaqt/_version.py index 168667c6..91df9d20 100644 --- a/pyvistaqt/_version.py +++ b/pyvistaqt/_version.py @@ -1,6 +1,6 @@ """Version info for pyvistaqt.""" # major, minor, patch -version_info = 0, 2, 0 +VERSION_INFO = 0, 2, 0 # Nice string for the version -__version__ = ".".join(map(str, version_info)) +__version__ = ".".join(map(str, VERSION_INFO)) diff --git a/pyvistaqt/counter.py b/pyvistaqt/counter.py index 30478312..56bd6199 100644 --- a/pyvistaqt/counter.py +++ b/pyvistaqt/counter.py @@ -1,8 +1,15 @@ +""" +This module contains a basic Qt-compatible counter class. +""" from PyQt5.QtCore import QObject, pyqtSignal, pyqtSlot class Counter(QObject): - """Counter augmented with a Qt timer.""" + """ + Counter class with Qt signal/slot. + """ + + # pylint: disable=too-few-public-methods signal_finished = pyqtSignal() diff --git a/pyvistaqt/dialog.py b/pyvistaqt/dialog.py index 89bb0517..b2fa6d0b 100644 --- a/pyvistaqt/dialog.py +++ b/pyvistaqt/dialog.py @@ -1,3 +1,6 @@ +""" +This module contains Qt dialog widgets. +""" import os from PyQt5 import QtCore @@ -19,6 +22,8 @@ class FileDialog(QFileDialog): the dialog was property closed. """ + # pylint: disable=too-few-public-methods + dlg_accepted = pyqtSignal(str) def __init__( @@ -29,7 +34,7 @@ def __init__( show=True, callback=None, directory=False, - ): + ): # pylint: disable=too-many-arguments """Initialize the file dialog.""" super(FileDialog, self).__init__(parent) @@ -96,13 +101,13 @@ def value(self): float(super().value()) / self._max_int * self._value_range + self._min_value ) - def setValue(self, value): + def setValue(self, value): # pylint: disable=invalid-name """Set the value of the slider.""" super().setValue( int((value - self._min_value) / self._value_range * self._max_int) ) - def setMinimum(self, value): + def setMinimum(self, value): # pylint: disable=invalid-name """Set the minimum value of the slider.""" if value > self._max_value: # pragma: no cover raise ValueError("Minimum limit cannot be higher than maximum") @@ -110,7 +115,7 @@ def setMinimum(self, value): self._min_value = value self.setValue(self.value()) - def setMaximum(self, value): + def setMaximum(self, value): # pylint: disable=invalid-name """Set the maximum value of the slider.""" if value < self._min_value: # pragma: no cover raise ValueError("Minimum limit cannot be higher than maximum") @@ -124,7 +129,9 @@ def setMaximum(self, value): class RangeGroup(QHBoxLayout): """Range group box widget.""" - def __init__(self, parent, callback, minimum=0.0, maximum=20.0, value=1.0): + def __init__( + self, parent, callback, minimum=0.0, maximum=20.0, value=1.0 + ): # pylint: disable=too-many-arguments """Initialize the range widget.""" super(RangeGroup, self).__init__(parent) self.slider = DoubleSlider(QtCore.Qt.Horizontal) @@ -148,11 +155,11 @@ def __init__(self, parent, callback, minimum=0.0, maximum=20.0, value=1.0): self.spinbox.valueChanged.connect(self.update_value) self.spinbox.valueChanged.connect(callback) - def update_spinbox(self, value): + def update_spinbox(self, value): # pylint: disable=unused-argument """Set the value of the internal spinbox.""" self.spinbox.setValue(self.slider.value()) - def update_value(self, value): + def update_value(self, value): # pylint: disable=unused-argument """Update the value of the internal slider.""" # if self.spinbox.value() < self.minimum: # self.spinbox.setValue(self.minimum) @@ -175,7 +182,11 @@ def value(self, new_value): class ScaleAxesDialog(QDialog): - """Dialog to control axes scaling.""" + """ + Dialog to control axes scaling. + """ + + # pylint: disable=too-few-public-methods accepted = pyqtSignal(float) signal_close = pyqtSignal() @@ -209,7 +220,7 @@ def __init__(self, parent, plotter, show=True): if show: # pragma: no cover self.show() - def update_scale(self, value): + def update_scale(self): """Update the scale of all actors in the plotter.""" self.plotter.set_scale( self.x_slider_group.value, diff --git a/pyvistaqt/plotting.py b/pyvistaqt/plotting.py index 50cc89ef..e9afd2cf 100644 --- a/pyvistaqt/plotting.py +++ b/pyvistaqt/plotting.py @@ -48,7 +48,7 @@ import pyvista import scooby import vtk -from PyQt5 import QtCore, QtGui +from PyQt5 import QtCore from PyQt5.QtCore import QTimer, pyqtSignal from PyQt5.QtWidgets import QAction, QFrame, QMenuBar, QVBoxLayout from pyvista.plotting.plotting import BasePlotter @@ -60,9 +60,15 @@ from .dialog import FileDialog, ScaleAxesDialog from .window import MainWindow -log = logging.getLogger("pyvistaqt") -log.setLevel(logging.CRITICAL) -log.addHandler(logging.StreamHandler()) +if scooby.in_ipython(): # pragma: no cover + from IPython import get_ipython + from IPython.external.qt_for_kernel import QtGui +else: + from PyQt5 import QtGui # pylint: disable=ungrouped-imports + +LOG = logging.getLogger("pyvistaqt") +LOG.setLevel(logging.CRITICAL) +LOG.addHandler(logging.StreamHandler()) # for display bugs due to older intel integrated GPUs, setting @@ -71,9 +77,9 @@ # changing it from the default 'QWidget'. # See https://github.com/pyvista/pyvista/pull/693 -# log is unused at the moment -# log = logging.getLogger(__name__) -# log.setLevel('DEBUG') +# LOG is unused at the moment +# LOG = logging.getLogger(__name__) +# LOG.setLevel('DEBUG') SAVE_CAM_BUTTON_TEXT = "Save Camera" CLEAR_CAMS_BUTTON_TEXT = "Clear Cameras" @@ -83,14 +89,14 @@ def resample_image(arr, max_size=400): """Resample a square image to an image of max_size.""" dim = np.max(arr.shape[0:2]) max_size = min(max_size, dim) - x, y, _ = arr.shape - sx = int(np.ceil(x / max_size)) - sy = int(np.ceil(y / max_size)) + x_size, y_size, _ = arr.shape + s_x = int(np.ceil(x_size / max_size)) + s_y = int(np.ceil(y_size / max_size)) img = np.zeros((max_size, max_size, arr.shape[2]), dtype=arr.dtype) - arr = arr[0:-1:sx, 0:-1:sy, :] - xl = (max_size - arr.shape[0]) // 2 - yl = (max_size - arr.shape[1]) // 2 - img[xl : arr.shape[0] + xl, yl : arr.shape[1] + yl, :] = arr + arr = arr[0:-1:s_x, 0:-1:s_y, :] + x_l = (max_size - arr.shape[0]) // 2 + y_l = (max_size - arr.shape[1]) // 2 + img[x_l : arr.shape[0] + x_l, y_l : arr.shape[1] + y_l, :] = arr return img @@ -98,14 +104,14 @@ def pad_image(arr, max_size=400): """Pad an image to a square then resamples to max_size.""" dim = np.max(arr.shape) img = np.zeros((dim, dim, arr.shape[2]), dtype=arr.dtype) - xl = (dim - arr.shape[0]) // 2 - yl = (dim - arr.shape[1]) // 2 - img[xl : arr.shape[0] + xl, yl : arr.shape[1] + yl, :] = arr + x_l = (dim - arr.shape[0]) // 2 + y_l = (dim - arr.shape[1]) // 2 + img[x_l : arr.shape[0] + x_l, y_l : arr.shape[1] + y_l, :] = arr return resample_image(img, max_size=max_size) @contextlib.contextmanager -def _no_BasePlotter_init(): +def _no_base_plotter_init(): init = BasePlotter.__init__ BasePlotter.__init__ = lambda x: None try: @@ -147,6 +153,9 @@ class QtInteractor(QVTKRenderWindowInteractor, BasePlotter): being automatically ``Modified``. """ + # pylint: disable=too-many-instance-attributes + # pylint: disable=too-many-statements + # Signals must be class attributes render_signal = pyqtSignal() key_press_event_signal = pyqtSignal(vtk.vtkGenericRenderWindowInteractor, str) @@ -160,12 +169,24 @@ def __init__( line_smoothing=False, point_smoothing=False, polygon_smoothing=False, - splitting_position=None, auto_update=5.0, **kwargs - ): + ): # pylint: disable=too-many-arguments + # pylint: disable=too-many-branches """Initialize Qt interactor.""" - log.debug("QtInteractor init start") + LOG.debug("QtInteractor init start") + + self.url = None + self.default_camera_tool_bar = None + self.saved_camera_positions = None + self.saved_camera_positions = None + self.saved_cameras_tool_bar = None + self.main_menu = None + self._menu_close_action = None + self._edl_action = None + self._parallel_projection_action = None + self._view_action = None + # Cannot use super() here because # QVTKRenderWindowInteractor silently swallows all kwargs # because they use **kwargs in their constructor... @@ -173,7 +194,7 @@ def __init__( for key in ("stereo", "iren", "rw", "wflags"): if key in kwargs: qvtk_kwargs[key] = kwargs.pop(key) - with _no_BasePlotter_init(): + with _no_base_plotter_init(): QVTKRenderWindowInteractor.__init__(self, **qvtk_kwargs) BasePlotter.__init__(self, **kwargs) # backward compat for when we had this as a separate class @@ -241,7 +262,7 @@ def __init__( self._first_time = False # Crucial! self.view_isometric() - log.debug("QtInteractor init stop") + LOG.debug("QtInteractor init stop") def gesture_event(self, event): """Handle gesture events.""" @@ -265,32 +286,31 @@ def render(self): """Override the ``render`` method to handle threading issues.""" return self.render_signal.emit() - def dragEnterEvent(self, event): + def dragEnterEvent(self, event): # pylint: disable=invalid-name,no-self-use """Event is called when something is dropped onto the vtk window. - Only triggers event when event contains file paths that exist. User can drop anything in this window and we only want to allow files. """ + # pragma: no cover try: for url in event.mimeData().urls(): if os.path.isfile(url.path()): # only call accept on files event.accept() - except Exception as e: - warnings.warn("Exception when dropping files: %s" % str(e)) + except IOError as exception: # pragma: no cover + warnings.warn("Exception when dropping files: %s" % str(exception)) - def dropEvent(self, event): + def dropEvent(self, event): # pylint: disable=invalid-name """Event is called after dragEnterEvent.""" - for url in event.mimeData().urls(): + for url in event.mimeData().urls(): # pragma: no cover self.url = url filename = self.url.path() if os.path.isfile(filename): try: self.add_mesh(pyvista.read(filename)) - except Exception as e: - print(str(e)) - pass + except IOError as exception: + print(str(exception)) def add_toolbars(self): """Add the toolbars.""" @@ -319,6 +339,7 @@ def _view_vector(*args): } for key, method in cvec_setters.items(): self._view_action = _add_action(self.default_camera_tool_bar, key, method) + # pylint: disable=unnecessary-lambda _add_action(self.default_camera_tool_bar, "Reset", lambda: self.reset_camera()) # Saved camera locations toolbar @@ -336,8 +357,6 @@ def _view_vector(*args): self.clear_camera_positions, ) - return - def add_menu_bar(self): """Add the main menu bar.""" self.main_menu = _create_menu_bar(parent=self.app_window) @@ -395,6 +414,7 @@ def save_camera_position(self): if hasattr(self, "saved_cameras_tool_bar"): def load_camera_position(): + # pylint: disable=attribute-defined-outside-init self.camera_position = camera_position self.saved_cameras_tool_bar.addAction( @@ -402,7 +422,6 @@ def load_camera_position(): ) if ncam < 10: self.add_key_event(str(ncam), load_camera_position) - return def clear_camera_positions(self): """Clear all camera positions.""" @@ -411,7 +430,6 @@ def clear_camera_positions(self): if action.text() not in [SAVE_CAM_BUTTON_TEXT, CLEAR_CAMS_BUTTON_TEXT]: self.saved_cameras_tool_bar.removeAction(action) self.saved_camera_positions = [] - return def close(self): """Quit application.""" @@ -490,6 +508,10 @@ class BackgroundPlotter(QtInteractor): >>> _ = plotter.add_mesh(pv.Sphere()) """ + # pylint: disable=too-many-ancestors + # pylint: disable=too-many-instance-attributes + # pylint: disable=too-many-statements + ICON_TIME_STEP = 5.0 def __init__( @@ -503,9 +525,10 @@ def __init__( menu_bar=True, update_app_icon=False, **kwargs - ): + ): # pylint: disable=too-many-arguments + # pylint: disable=too-many-branches """Initialize the qt plotter.""" - log.debug("BackgroundPlotter init start") + LOG.debug("BackgroundPlotter init start") if not isinstance(menu_bar, bool): raise TypeError( "Expected type for ``menu_bar`` is bool" @@ -529,19 +552,17 @@ def __init__( # ipython magic if scooby.in_ipython(): # pragma: no cover - from IPython import get_ipython ipython = get_ipython() ipython.magic("gui qt") - from IPython.external.qt_for_kernel import QtGui - QtGui.QApplication.instance() else: ipython = None # run within python if app is None: + # pylint: disable=import-outside-toplevel from PyQt5.QtWidgets import QApplication app = QApplication.instance() @@ -602,7 +623,7 @@ def __init__( # Keypress events self.add_key_event("S", self._qt_screenshot) # shift + s - log.debug("BackgroundPlotter init stop") + LOG.debug("BackgroundPlotter init stop") def reset_key_events(self): """Reset all of the key press events to their defaults. @@ -611,6 +632,7 @@ def reset_key_events(self): """ super(BackgroundPlotter, self).reset_key_events() if self.allow_quit_keypress: + # pylint: disable=unnecessary-lambda self.add_key_event("q", lambda: self.close()) def scale_axes_dialog(self, show=True): diff --git a/pyvistaqt/window.py b/pyvistaqt/window.py index 1c37b10f..c595539a 100644 --- a/pyvistaqt/window.py +++ b/pyvistaqt/window.py @@ -1,21 +1,27 @@ +""" +This module contains a Qt-compatible MainWindow class. +""" from PyQt5 import QtCore from PyQt5.QtCore import pyqtSignal from PyQt5.QtWidgets import QMainWindow class MainWindow(QMainWindow): - """Convenience MainWindow that manages the application.""" + """ + Convenience MainWindow that manages the application. + """ signal_close = pyqtSignal() signal_gesture = pyqtSignal(QtCore.QEvent) def event(self, event): - if event.type() == QtCore.QEvent.Gesture: + """Manage window events and filter the gesture event.""" + if event.type() == QtCore.QEvent.Gesture: # pragma: no cover self.signal_gesture.emit(event) return True return super().event(event) - def closeEvent(self, event): + def closeEvent(self, event): # pylint: disable=invalid-name """Manage the close event.""" self.signal_close.emit() event.accept()