From 50b0cad61499ee5ddab1de8538f78cbaafe99cd1 Mon Sep 17 00:00:00 2001 From: Jason Madden Date: Thu, 21 Dec 2023 12:52:42 -0600 Subject: [PATCH] Linting. Add linting to CI. --- .github/workflows/tests.yml | 8 ++ .pylintrc | 131 ++++++++++++++++---- CHANGES.rst | 22 ++-- setup.py | 1 + src/greenlet/__init__.py | 2 +- src/greenlet/tests/leakcheck.py | 1 + src/greenlet/tests/test_contextvars.py | 1 + src/greenlet/tests/test_gc.py | 2 +- src/greenlet/tests/test_generator_nested.py | 2 +- src/greenlet/tests/test_throw.py | 3 +- src/greenlet/tests/test_weakref.py | 2 +- 11 files changed, 133 insertions(+), 42 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index dfcfcea7..66b2e458 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -89,6 +89,14 @@ jobs: - name: Doctest run: | sphinx-build -b doctest -d docs/_build/doctrees2 docs docs/_build/doctest2 + - name: Lint + if: matrix.python-version == '3.10' && startsWith(runner.os, 'Linux') + # We only need to do this on one version. + # We do this here rather than a separate job to avoid the compilation overhead. + run: | + pip install -U pylint + python -m pylint --rcfile=.pylintrc greenlet + - name: Publish package to PyPI (mac) # We cannot 'uses: pypa/gh-action-pypi-publish@v1.4.1' because # that's apparently a container action, and those don't run on diff --git a/.pylintrc b/.pylintrc index 02a2156f..ce76713b 100644 --- a/.pylintrc +++ b/.pylintrc @@ -1,9 +1,85 @@ [MASTER] -load-plugins=pylint.extensions.bad_builtin +load-plugins=pylint.extensions.bad_builtin, + pylint.extensions.code_style, + pylint.extensions.dict_init_mutate, + pylint.extensions.dunder, + pylint.extensions.comparison_placement, + pylint.extensions.confusing_elif, + pylint.extensions.for_any_all, + pylint.extensions.consider_refactoring_into_while_condition, + pylint.extensions.check_elif, + pylint.extensions.eq_without_hash, + pylint.extensions.overlapping_exceptions, + +# pylint.extensions.comparetozero, +# Takes out ``if x == 0:`` and wants you to write ``if not x:`` +# but in many cases, the == 0 is actually much more clear. + +# pylint.extensions.mccabe, +# We have too many too-complex methods. We should enable this and fix them +# one by one. + +# pylint.extensions.redefined_variable_type, +# We use that pattern during initialization. + +# magic_value wants you to not use arbitrary strings and numbers +# inline in the code. But it's overzealous and has way too many false +# positives. Trust people to do the most readable thing. +# pylint.extensions.magic_value + +# Empty comment would be good, except it detects blank lines within +# a single comment block. +# +# Those are often used to separate paragraphs, like here. +# pylint.extensions.empty_comment, + +# consider_ternary_expression is a nice check, but is also overzealous. +# Trust the human to do the readable thing. +# pylint.extensions.consider_ternary_expression, + +# redefined_loop_name tends to catch us with things like +# for name in (a, b, c): name = name + '_column' ... +# pylint.extensions.redefined_loop_name, + +# This wants you to turn ``x in (1, 2)`` into ``x in {1, 2}``. +# They both result in the LOAD_CONST bytecode, one a tuple one a +# frozenset. In theory a set lookup using hashing is faster than +# a linear scan of a tuple; but if the tuple is small, it can often +# actually be faster to scan the tuple. +# pylint.extensions.set_membership, + # Fix zope.cachedescriptors.property.Lazy; the property-classes doesn't seem to # do anything. # https://stackoverflow.com/questions/51160955/pylint-how-to-specify-a-self-defined-property-decorator-with-property-classes -init-hook = "import astroid.bases; astroid.bases.POSSIBLE_PROPERTIES.add('Lazy')" +# For releases prior to 2.14.2, this needs to be a one-line, quoted string. After that, +# a multi-line string. +# - Make zope.cachedescriptors.property.Lazy look like a property; +# fixes pylint thinking it is a method. +# - Run in Pure Python mode (ignore C extensions that respect this); +# fixes some issues with zope.interface, like IFoo.providedby(ob) +# claiming not to have the right number of parameters...except no, it does not. +init-hook = + import astroid.bases + astroid.bases.POSSIBLE_PROPERTIES.add('Lazy') + astroid.bases.POSSIBLE_PROPERTIES.add('LazyOnClass') + astroid.bases.POSSIBLE_PROPERTIES.add('readproperty') + astroid.bases.POSSIBLE_PROPERTIES.add('non_overridable') + import os + os.environ['PURE_PYTHON'] = ("1") + # Ending on a quoted string + # breaks pylint 2.14.5 (it strips the trailing quote. This is + # probably because it tries to handle one-line quoted strings as well as multi-blocks). + # The parens around it fix the issue. + +extension-pkg-whitelist=greenlet._greenlet + +# 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. +# gevent: The changes for Python 3.7 in _ssl3.py lead to infinite recursion +# in pylint 2.3.1/astroid 2.2.5 in that file unless we this this to 1 +# from the default of 100. +limit-inference-results=1 [MESSAGES CONTROL] @@ -49,31 +125,55 @@ init-hook = "import astroid.bases; astroid.bases.POSSIBLE_PROPERTIES.add('Lazy') # Pylint 2.4 adds self-assigning-variable. But we do *that* to avoid unused-import when we # "export" the variable and don't have a __all__. # Pylint 2.6+ adds some python-3-only things that don't apply: raise-missing-from, super-with-arguments, consider-using-f-string, redundant-u-string-prefix +# unnecessary-lambda-assignment: New check introduced in v2.14.0 +# unnecessary-dunder-call: New check introduced in v2.14.0 +# consider-using-assignment-expr: wants you to use the walrus operator. +# It hits way too much and its not clear they would be improvements. +# confusing-consecutive-elif: Are they though? disable=wrong-import-position, wrong-import-order, missing-docstring, ungrouped-imports, invalid-name, + protected-access, too-few-public-methods, + exec-used, global-statement, + multiple-statements, locally-disabled, + cyclic-import, too-many-arguments, + redefined-builtin, useless-suppression, duplicate-code, + undefined-all-variable, + inconsistent-return-statements, + useless-return, useless-object-inheritance, import-outside-toplevel, self-assigning-variable, - consider-using-f-string + raise-missing-from, + super-with-arguments, + consider-using-f-string, + consider-using-assignment-expr, + redundant-u-string-prefix, + unnecessary-lambda-assignment, + unnecessary-dunder-call, + use-dict-literal, + confusing-consecutive-elif, + +enable=consider-using-augmented-assign [FORMAT] -max-line-length=100 +# duplicated from setup.cfg +max-line-length=160 max-module-lines=1100 [MISCELLANEOUS] # List of note tags to take in consideration, separated by a comma. #notes=FIXME,XXX,TODO -# Disable that, we don't want them to fail the lint CI job. +# Disable that, we don't want them in the report (???) notes= [VARIABLES] @@ -85,14 +185,8 @@ dummy-variables-rgx=_.* # 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=REQUEST,acl_users,aq_parent,providedBy - - -# Tells whether missing members accessed in mixin class should be ignored. A -# mixin class is detected if its name ends with "mixin" (case insensitive). -# XXX: deprecated in 2.14; replaced with ignored-checks-for-mixins. -# The defaults for that value seem to be what we want -#ignore-mixin-members=yes +# gevent: this is helpful for py3/py2 code. +generated-members=exc_clear # List of classes names for which member attributes should not be checked # (useful for classes with attributes dynamically set). This can work @@ -104,25 +198,18 @@ generated-members=REQUEST,acl_users,aq_parent,providedBy # (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=gevent._corecffi,gevent.os,os,greenlet,threading,gevent.libev.corecffi,gevent.socket,gevent.core,gevent.testing.support +ignored-modules=gevent._corecffi,gevent.os,os,greenlet,threading,gevent.libev.corecffi,gevent.socket,gevent.core,gevent.testing.support [DESIGN] max-attributes=12 max-parents=10 [BASIC] +bad-functions=input # Prospector turns ot unsafe-load-any-extension by default, but # pylint leaves it off. This is the proximal cause of the # undefined-all-variable crash. unsafe-load-any-extension = yes -property-classes=zope.cachedescriptors.property.Lazy,zope.cachedescriptors.property.Cached -extension-pkg-allow-list=greenlet._greenlet - -[CLASSES] -# List of interface methods to ignore, separated by a comma. This is used for -# instance to not check methods defines in Zope's Interface base class. - - # Local Variables: # mode: conf diff --git a/CHANGES.rst b/CHANGES.rst index 1c5e145b..f1378316 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -2,20 +2,14 @@ Changes ========= -3.1.0 (unreleased) +3.0.3 (unreleased) ================== - Python 3.12: Restore the full ability to walk the stack of a suspended - greenlet; previously only the innermost frame was exposed. - For performance reasons, there are still some restrictions relative to - older Python versions; in particular, by default it is only valid to walk - a suspended greenlet's stack in between a ``gr_frame`` access and the next - resumption of the greenlet. A more flexible mode can be enabled by setting - the greenlet's new ``gr_frames_always_exposed`` attribute to True. If you - get a Python interpreter crash on 3.12+ when accessing the ``f_back`` of a - suspended greenlet frame, you're probably accessing it in a way that - requires you to set this attribute. See `issue 388 - `_. + greenlet; previously only the innermost frame was exposed. See `issue 388 + `_. Fix by + Joshua Oreman in `PR 393 + `_. 3.0.2 (2023-12-08) ================== @@ -251,7 +245,7 @@ Known Issues ==================== Platforms -~~~~~~~~~ +--------- - Add experimental, untested support for 64-bit Windows on ARM using MSVC. See `PR 271 `_. @@ -401,7 +395,7 @@ Changes - (Documentation) Publish the change log to https://greenlet.readthedocs.io Supported Platforms -~~~~~~~~~~~~~~~~~~~ +------------------- - Drop support for Python 2.4, 2.5, 2.6, 3.0, 3.1, 3.2 and 3.4. The project metadata now includes the ``python_requires`` data to @@ -411,7 +405,7 @@ Supported Platforms `_. Packaging Changes -~~~~~~~~~~~~~~~~~ +----------------- - Require setuptools to build from source. - Stop asking setuptools to build both .tar.gz and .zip diff --git a/setup.py b/setup.py index cec1986f..541bb9b9 100755 --- a/setup.py +++ b/setup.py @@ -247,6 +247,7 @@ def get_greenlet_version(): extras_require={ 'docs': [ 'Sphinx', + 'furo', ], 'test': [ 'objgraph', diff --git a/src/greenlet/__init__.py b/src/greenlet/__init__.py index c44b15e8..12a4c4fd 100644 --- a/src/greenlet/__init__.py +++ b/src/greenlet/__init__.py @@ -25,7 +25,7 @@ ### # Metadata ### -__version__ = '3.1.0.dev0' +__version__ = '3.0.3.dev0' from ._greenlet import _C_API # pylint:disable=no-name-in-module ### diff --git a/src/greenlet/tests/leakcheck.py b/src/greenlet/tests/leakcheck.py index 79a18fce..a5152fb2 100644 --- a/src/greenlet/tests/leakcheck.py +++ b/src/greenlet/tests/leakcheck.py @@ -220,6 +220,7 @@ def _run_test(self, args, kwargs): def _growth_after(self): # Grab post snapshot + # pylint:disable=no-member if 'urlparse' in sys.modules: sys.modules['urlparse'].clear_cache() if 'urllib.parse' in sys.modules: diff --git a/src/greenlet/tests/test_contextvars.py b/src/greenlet/tests/test_contextvars.py index 4f7f98d2..9a16f671 100644 --- a/src/greenlet/tests/test_contextvars.py +++ b/src/greenlet/tests/test_contextvars.py @@ -47,6 +47,7 @@ def _increment(self, greenlet_id, callback, counts, expect): callback() def _test_context(self, propagate_by): + # pylint:disable=too-many-branches ID_VAR.set(0) callback = getcurrent().switch diff --git a/src/greenlet/tests/test_gc.py b/src/greenlet/tests/test_gc.py index 43927d45..994addb9 100644 --- a/src/greenlet/tests/test_gc.py +++ b/src/greenlet/tests/test_gc.py @@ -23,7 +23,7 @@ def test_dead_circular_ref(self): def test_circular_greenlet(self): class circular_greenlet(greenlet.greenlet): - pass + self = None o = circular_greenlet() o.self = o o = weakref.ref(o) diff --git a/src/greenlet/tests/test_generator_nested.py b/src/greenlet/tests/test_generator_nested.py index 0c5d7466..8d752a63 100644 --- a/src/greenlet/tests/test_generator_nested.py +++ b/src/greenlet/tests/test_generator_nested.py @@ -149,7 +149,7 @@ def test_permutations(self): # XXX Test to make sure we are working as a generator expression def test_genlet_simple(self): - for g in [g1, g2, g3]: + for g in g1, g2, g3: seen = [] for _ in range(3): for j in g(5, seen): diff --git a/src/greenlet/tests/test_throw.py b/src/greenlet/tests/test_throw.py index 90d657a2..f4f9a140 100644 --- a/src/greenlet/tests/test_throw.py +++ b/src/greenlet/tests/test_throw.py @@ -67,8 +67,7 @@ def f1(): main.switch("f1 ready to catch") except IndexError: return "caught" - else: - return "normal exit" + return "normal exit" def f2(): main.switch("from f2") diff --git a/src/greenlet/tests/test_weakref.py b/src/greenlet/tests/test_weakref.py index 916ef8ae..05a38a7f 100644 --- a/src/greenlet/tests/test_weakref.py +++ b/src/greenlet/tests/test_weakref.py @@ -1,6 +1,6 @@ import gc import weakref -import unittest + import greenlet from . import TestCase