From 387d440b8c1c811919d8cf2188cc692a345055c5 Mon Sep 17 00:00:00 2001 From: Michael Howitz Date: Mon, 8 Apr 2019 08:20:27 +0200 Subject: Flake8 the code. (#18) While keeping coverage at 100 %. --- .travis.yml | 4 ++ CHANGES.rst | 2 + setup.cfg | 3 + setup.py | 115 ++++++++++++++++--------------- src/zope/tales/__init__.py | 1 - src/zope/tales/engine.py | 7 +- src/zope/tales/expressions.py | 15 ++-- src/zope/tales/interfaces.py | 2 + src/zope/tales/pythonexpr.py | 5 +- src/zope/tales/tales.py | 43 +++++++----- src/zope/tales/tests/simpleexpr.py | 7 +- src/zope/tales/tests/test_expressions.py | 31 ++++++--- src/zope/tales/tests/test_pythonexpr.py | 1 + src/zope/tales/tests/test_tales.py | 15 ++-- src/zope/tales/tests/test_traverser.py | 25 +++---- tox.ini | 8 ++- 16 files changed, 165 insertions(+), 119 deletions(-) diff --git a/.travis.yml b/.travis.yml index 035969a..b0627ed 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,6 +11,10 @@ matrix: dist: xenial - python: "3.8-dev" dist: xenial + - name: "flake8" + install: pip install flake8 + script: flake8 src setup.py + after_success: install: - pip install -U pip setuptools - pip install -U coverage coverage-python-version coveralls diff --git a/CHANGES.rst b/CHANGES.rst index 559616d..6faa270 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -10,6 +10,8 @@ - Fix test failures and deprecation warnings occurring when using Python 3.8a1. (`#15 `_) +- Flake8 the code. + 4.3 (2018-10-05) ================ diff --git a/setup.cfg b/setup.cfg index 2a9acf1..c1e4c65 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,2 +1,5 @@ [bdist_wheel] universal = 1 + +[flake8] +doctests = yes diff --git a/setup.py b/setup.py index 931a6bc..d8b3f5b 100644 --- a/setup.py +++ b/setup.py @@ -26,65 +26,68 @@ def read(*rnames): with open(os.path.join(os.path.dirname(__file__), *rnames)) as f: return f.read() + TESTS_REQUIRE = [ 'zope.testing', 'zope.testrunner', ] -setup(name='zope.tales', - version='5.0.dev0', - author='Zope Foundation and Contributors', - author_email='zope-dev@zope.org', - description='Zope Template Application Language Expression Syntax ' - '(TALES)', - long_description=( - read('README.rst') - + '\n\n' + - read('CHANGES.rst') - ), - keywords="zope template xml tales", - classifiers=[ - 'Development Status :: 5 - Production/Stable', - 'Environment :: Web Environment', - 'Intended Audience :: Developers', - 'License :: OSI Approved :: Zope Public License', - 'Programming Language :: Python', - 'Programming Language :: Python :: 2', - 'Programming Language :: Python :: 2.7', - 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.5', - 'Programming Language :: Python :: 3.6', - 'Programming Language :: Python :: 3.7', - 'Programming Language :: Python :: Implementation :: CPython', - 'Programming Language :: Python :: Implementation :: PyPy', - 'Natural Language :: English', - 'Operating System :: OS Independent', - 'Topic :: Internet :: WWW/HTTP', - 'Framework :: Zope :: 3', - ], - url='https://github.com/zopefoundation/zope.tales', - license='ZPL 2.1', - packages=find_packages('src'), - package_dir={'': 'src'}, - namespace_packages=['zope'], - python_requires='>=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*', - extras_require={ - 'test': TESTS_REQUIRE, - 'tal': [ - 'zope.tal', - ], - 'docs': [ - 'Sphinx', - 'repoze.sphinx.autointerface', - 'zope.tal', - ], - }, - install_requires=[ - 'setuptools', - 'zope.interface', - 'six', - ], - tests_require=TESTS_REQUIRE, - include_package_data=True, - zip_safe=False, + +setup( + name='zope.tales', + version='5.0.dev0', + author='Zope Foundation and Contributors', + author_email='zope-dev@zope.org', + description='Zope Template Application Language Expression Syntax ' + '(TALES)', + long_description=( + read('README.rst') + + '\n\n' + + read('CHANGES.rst') + ), + keywords="zope template xml tales", + classifiers=[ + 'Development Status :: 5 - Production/Stable', + 'Environment :: Web Environment', + 'Intended Audience :: Developers', + 'License :: OSI Approved :: Zope Public License', + 'Programming Language :: Python', + 'Programming Language :: Python :: 2', + 'Programming Language :: Python :: 2.7', + 'Programming Language :: Python :: 3', + 'Programming Language :: Python :: 3.5', + 'Programming Language :: Python :: 3.6', + 'Programming Language :: Python :: 3.7', + 'Programming Language :: Python :: Implementation :: CPython', + 'Programming Language :: Python :: Implementation :: PyPy', + 'Natural Language :: English', + 'Operating System :: OS Independent', + 'Topic :: Internet :: WWW/HTTP', + 'Framework :: Zope :: 3', + ], + url='https://github.com/zopefoundation/zope.tales', + license='ZPL 2.1', + packages=find_packages('src'), + package_dir={'': 'src'}, + namespace_packages=['zope'], + python_requires='>=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*', + extras_require={ + 'test': TESTS_REQUIRE, + 'tal': [ + 'zope.tal', + ], + 'docs': [ + 'Sphinx', + 'repoze.sphinx.autointerface', + 'zope.tal', + ], + }, + install_requires=[ + 'setuptools', + 'zope.interface', + 'six', + ], + tests_require=TESTS_REQUIRE, + include_package_data=True, + zip_safe=False, ) diff --git a/src/zope/tales/__init__.py b/src/zope/tales/__init__.py index 61cfd4c..ccf848b 100644 --- a/src/zope/tales/__init__.py +++ b/src/zope/tales/__init__.py @@ -13,4 +13,3 @@ ############################################################################## """Template Attribute Language - Expression Syntax """ - diff --git a/src/zope/tales/engine.py b/src/zope/tales/engine.py index 7bab8f2..dd07600 100644 --- a/src/zope/tales/engine.py +++ b/src/zope/tales/engine.py @@ -24,6 +24,7 @@ from zope.tales.expressions import LazyExpr from zope.tales.expressions import SimpleModuleImporter from zope.tales.pythonexpr import PythonExpr + def DefaultEngine(): """ Create and return an instance of :class:`~.ExpressionEngine` (an @@ -44,8 +45,9 @@ def DefaultEngine(): ``modules`` :class:`.SimpleModuleImporter` - In addition, the default ``path`` expressions (``standard``, ``path``, ``exists`` - and ``nocall``), all implemented by :class:`.PathExpr`, are registered. + In addition, the default ``path`` expressions (``standard``, ``path``, + ``exists`` and ``nocall``), all implemented by :class:`.PathExpr`, are + registered. """ e = ExpressionEngine() reg = e.registerType @@ -59,4 +61,5 @@ def DefaultEngine(): e.registerBaseName('modules', SimpleModuleImporter()) return e + Engine = DefaultEngine() diff --git a/src/zope/tales/expressions.py b/src/zope/tales/expressions.py index 48be495..81a626b 100644 --- a/src/zope/tales/expressions.py +++ b/src/zope/tales/expressions.py @@ -16,9 +16,9 @@ Basic Page Template expression types. Expression objects are created by the :class:`.ExpressionEngine` (they must have previously been registered with -:func:`~zope.tales.tales.ExpressionEngine.registerType`). The expression object itself -is a callable object taking one argument, *econtext*, which is the local -expression namespace. +:func:`~zope.tales.tales.ExpressionEngine.registerType`). The expression +object itself is a callable object taking one argument, *econtext*, which is +the local expression namespace. """ import re @@ -38,6 +38,7 @@ namespace_re = re.compile(r'(\w+):(.+)') PY2 = sys.version_info[0] == 2 + def simpleTraverse(object, path_items, econtext): """Traverses a sequence of names, first trying attributes then items. """ @@ -168,7 +169,7 @@ class PathExpr(object): 'path', 'exists', 'nocall', - ) + ) def __init__(self, name, expr, engine, traverser=simpleTraverse): self._s = expr @@ -240,11 +241,11 @@ class PathExpr(object): return '' % (self._name, repr(self._s)) - _interp = re.compile( r'\$(%(n)s)|\${(%(n)s(?:/[^}|]*)*(?:\|%(n)s(?:/[^}|]*)*)*)}' % {'n': NAME_RE}) + @implementer(ITALESExpression) class StringExpr(object): """ @@ -264,7 +265,8 @@ class StringExpr(object): path_type = engine.getTypes()['path'] parts = [] for exp in expr.split('$$'): - if parts: parts.append('$') + if parts: + parts.append('$') m = _interp.search(exp) while m is not None: parts.append(exp[:m.start()]) @@ -365,6 +367,7 @@ class LazyWrapper(DeferWrapper): self._result = r = self._expr(self._econtext) return r + class LazyExpr(DeferExpr): """ An expression that will defer evaluation of its diff --git a/src/zope/tales/interfaces.py b/src/zope/tales/interfaces.py index 158b794..58c234f 100644 --- a/src/zope/tales/interfaces.py +++ b/src/zope/tales/interfaces.py @@ -33,6 +33,7 @@ class ITALESFunctionNamespace(Interface): def setEngine(engine): """Sets the engine that is used to evaluate TALES expressions.""" + class ITALESExpression(Interface): """TALES expression @@ -46,6 +47,7 @@ class ITALESExpression(Interface): *econtext* and return computed value. """ + class ITALESIterator(ITALIterator): """TAL Iterator provided by TALES. diff --git a/src/zope/tales/pythonexpr.py b/src/zope/tales/pythonexpr.py index 3f47bed..01df726 100644 --- a/src/zope/tales/pythonexpr.py +++ b/src/zope/tales/pythonexpr.py @@ -14,6 +14,7 @@ """Generic Python Expression Handler """ + class PythonExpr(object): """ Evaluates a python expression by calling :func:`eval` after @@ -25,8 +26,8 @@ class PythonExpr(object): :param ExpressionEngine engine: The expression compiler that is creating us. """ - text = '\n'.join(expr.splitlines()) # normalize line endings - text = '(' + text + ')' # Put text in parens so newlines don't matter + text = '\n'.join(expr.splitlines()) # normalize line endings + text = '(' + text + ')' # Put text in parens so newlines don't matter self.text = text try: code = self._compile(text, '') diff --git a/src/zope/tales/tales.py b/src/zope/tales/tales.py index 21a3276..d5ca006 100644 --- a/src/zope/tales/tales.py +++ b/src/zope/tales/tales.py @@ -15,7 +15,6 @@ An implementation of a TAL expression engine """ -__docformat__ = "reStructuredText" import re try: @@ -29,23 +28,28 @@ import six from zope.tales.interfaces import ITALESIterator + class ITALExpressionEngine(Interface): pass + + class ITALExpressionCompiler(Interface): pass + + class ITALExpressionErrorInfo(Interface): pass + try: # Override with real, if present - from zope.tal.interfaces import (ITALExpressionEngine, + from zope.tal.interfaces import (ITALExpressionEngine, # noqa: F811 ITALExpressionCompiler, ITALExpressionErrorInfo) except ImportError: pass - NAME_RE = r"[a-zA-Z][a-zA-Z0-9_]*" _parse_expr = re.compile(r"(%s):" % NAME_RE).match _valid_name = re.compile('%s$' % NAME_RE).match @@ -54,18 +58,22 @@ _valid_name = re.compile('%s$' % NAME_RE).match class TALESError(Exception): """Error during TALES evaluation""" + class Undefined(TALESError): - '''Exception raised on traversal of an undefined path''' + """Exception raised on traversal of an undefined path.""" + class CompilerError(Exception): - '''TALES Compiler Error''' + """TALES Compiler Error""" + class RegistrationError(Exception): - '''Expression type or base name registration Error''' + """Expression type or base name registration Error.""" _default = object() + @implementer(ITALESIterator) class Iterator(object): """ @@ -568,8 +576,8 @@ class ExpressionEngine(object): :param str namespace: a string containing the name of the namespace to be registered - :param callable namespacecallable: a callable object which takes the following - parameter: + :param callable namespacecallable: a callable object which takes the + following parameter: :context: the object on which the functions provided by this namespace will @@ -596,7 +604,6 @@ class ExpressionEngine(object): """ self.namespaces[namespacename] = namespacecallable - def getFunctionNamespace(self, namespacename): """ Returns the function namespace """ return self.namespaces[namespacename] @@ -605,11 +612,12 @@ class ExpressionEngine(object): """ Register an expression of *name* to be handled with *handler*. - :raises RegistrationError: If this is a duplicate registration for *name*, - or if *name* is not a valid expression type name. + :raises RegistrationError: If this is a duplicate registration for + *name*, or if *name* is not a valid expression type name. """ if not _valid_name(name): - raise RegistrationError('Invalid expression type name "%s".' % name) + raise RegistrationError( + 'Invalid expression type name "%s".' % name) types = self.types if name in types: raise RegistrationError( @@ -674,8 +682,8 @@ class Context(object): :class:`zope.tal.interfaces.ITALExpressionEngine`. This class is called ``Context`` because an instance of this class - holds context information (namespaces) that it uses when evaluating compiled - expressions. + holds context information (namespaces) that it uses when evaluating + compiled expressions. """ position = (None, None) source_file = None @@ -695,7 +703,7 @@ class Context(object): self.repeat_vars = rv = {} # Wrap this, as it is visible to restricted code self.setContext('repeat', rv) - self.setContext('loop', rv) # alias + self.setContext('loop', rv) # alias self.vars = vars = contexts.copy() self._vars_stack = [vars] @@ -704,7 +712,8 @@ class Context(object): self._scope_stack = [] def setContext(self, name, value): - """Hook to allow subclasses to do things like adding security proxies.""" + """Hook to allow subclasses to do things like adding security proxies. + """ self.contexts[name] = value def beginScope(self): @@ -822,7 +831,7 @@ class TALESTracebackSupplement(object): import pprint data = self.context.contexts.copy() if 'modules' in data: - del data['modules'] # the list is really long and boring + del data['modules'] # the list is really long and boring s = pprint.pformat(data) if not as_html: return ' - Names:\n %s' % s.replace('\n', '\n ') diff --git a/src/zope/tales/tests/simpleexpr.py b/src/zope/tales/tests/simpleexpr.py index 7befa35..ac5409c 100644 --- a/src/zope/tales/tests/simpleexpr.py +++ b/src/zope/tales/tests/simpleexpr.py @@ -14,15 +14,16 @@ """Simple TALES Expression """ + class SimpleExpr(object): - '''Simple example of an expression type handler + """Simple example of an expression type handler for testing.""" - for testing - ''' def __init__(self, name, expr, engine): self._name = name self._expr = expr + def __call__(self, econtext): return self._name, self._expr + def __repr__(self): return '' % (self._name, repr(self._expr)) diff --git a/src/zope/tales/tests/test_expressions.py b/src/zope/tales/tests/test_expressions.py index 94f8ecf..e39bdf1 100644 --- a/src/zope/tales/tests/test_expressions.py +++ b/src/zope/tales/tests/test_expressions.py @@ -32,8 +32,8 @@ class Data(object): self.__dict__.update(kw) def __getattr__(self, name): - # Let linters (like pylint) know this is a dynamic class and they shouldn't - # emit "Data has no attribute" errors + # Let linters (like pylint) know this is a dynamic class and they + # shouldn't emit "Data has no attribute" errors return object.__getattribute__(self, name) def __repr__(self): @@ -41,6 +41,7 @@ class Data(object): __str__ = __repr__ + class ErrorGenerator(object): def __getitem__(self, name): @@ -51,15 +52,17 @@ class ErrorGenerator(object): e = getattr(builtins, name, None) or SystemError raise e('mess') + class Callable(object): def __call__(self): return 42 -class OldStyleCallable: # NOT object +class OldStyleCallable: # NOT object pass + class ExpressionTestBase(zope.tales.tests.TestCase): def setUp(self): @@ -199,6 +202,10 @@ class TestParsedExpressions(ExpressionTestBase): expr = self.engine.compile('string:A$B') self._check_evals_to(expr, 'A2') + def testString_w_dollar_sign(self): + expr = self.engine.compile('string:A$$$B') + self._check_evals_to(expr, 'A$2') + def testStringSub_w_python(self): CompilerError = self.engine.getCompilerError() self.assertRaises(CompilerError, @@ -220,7 +227,8 @@ class TestParsedExpressions(ExpressionTestBase): # Simple eight bit string interpolation should just work. # Except on Py3, where we really mess it up. expr = self.engine.compile('string:a ${eightBits}') - expected = 'a ' + self.context.vars['eightBits'] if not six.PY3 else self.py3BrokenEightBits + expected = ('a ' + self.context.vars['eightBits'] + if not six.PY3 else self.py3BrokenEightBits) self._check_evals_to(expr, expected) def testStringUnicode(self): @@ -241,7 +249,8 @@ class TestParsedExpressions(ExpressionTestBase): # poorly. self.assertTrue(six.PY3) self.assertEqual(result, self.py3BrokenEightBits) - self.context.vars['eightBits'].decode('ascii') # raise UnicodeDecodeError + # raise UnicodeDecodeError + self.context.vars['eightBits'].decode('ascii') def test_string_escape_percent(self): self._check_evals_to('string:%', '%') @@ -283,6 +292,7 @@ class TestParsedExpressions(ExpressionTestBase): def testEmptyPathSegmentRaisesCompilerError(self): CompilerError = self.engine.getCompilerError() + def check(expr): self.assertRaises(CompilerError, self.engine.compile, expr) @@ -394,7 +404,7 @@ class FunctionTests(ExpressionTestBase): self.engine.registerFunctionNamespace('namespace', self.TestNameSpace) self.engine.registerFunctionNamespace('not_callable_ns', None) - ## framework-ish tests + # framework-ish tests def testSetEngine(self): expr = self.engine.compile('adapterTest/namespace:engine') @@ -411,7 +421,7 @@ class FunctionTests(ExpressionTestBase): self.engine.getFunctionNamespace, 'badnamespace') - ## compile time tests + # compile time tests def testBadNamespace(self): # namespace doesn't exist @@ -457,14 +467,15 @@ class FunctionTests(ExpressionTestBase): e = exc.exception self.assertEqual(e.args[0], 'title') - ## runtime tests + # runtime tests def testNormalFunction(self): expr = self.engine.compile('adapterTest/namespace:upper') self.assertEqual(expr(self.context), 'YIKES') def testFunctionOnFunction(self): - expr = self.engine.compile('adapterTest/namespace:jump/namespace:upper') + expr = self.engine.compile( + 'adapterTest/namespace:jump/namespace:upper') self.assertEqual(expr(self.context), 'XANDER') def testPathOnFunction(self): @@ -476,6 +487,7 @@ class FunctionTests(ExpressionTestBase): with self.assertRaisesRegex(ValueError, 'None'): expr(self.context) + class TestSimpleModuleImporter(unittest.TestCase): def _makeOne(self): @@ -493,7 +505,6 @@ class TestSimpleModuleImporter(unittest.TestCase): with self.assertRaises(ImportError): self._makeOne()['this cannot exist'] - def test_no_such_submodule_not_package(self): with self.assertRaises(ImportError): self._makeOne()['zope.tales.tests.test_expressions.submodule'] diff --git a/src/zope/tales/tests/test_pythonexpr.py b/src/zope/tales/tests/test_pythonexpr.py index 7410c41..edc8764 100644 --- a/src/zope/tales/tests/test_pythonexpr.py +++ b/src/zope/tales/tests/test_pythonexpr.py @@ -20,6 +20,7 @@ from zope.tales.tales import Context from zope.tales.pythonexpr import PythonExpr from zope.tales.pythonexpr import ExprTypeProxy + class TestPythonExpr(unittest.TestCase): def setUp(self): diff --git a/src/zope/tales/tests/test_tales.py b/src/zope/tales/tests/test_tales.py index 1781624..03a1fe5 100644 --- a/src/zope/tales/tests/test_tales.py +++ b/src/zope/tales/tests/test_tales.py @@ -16,6 +16,7 @@ from doctest import DocTestSuite import unittest import re +import sys import six from zope.tales import tales @@ -52,6 +53,7 @@ class TestIterator(unittest.TestCase): self.assertTrue(not next(it), "Multi-element iterator") context._complete_() + class TALESTests(unittest.TestCase): def testRegisterType(self): @@ -150,6 +152,7 @@ class TALESTests(unittest.TestCase): ctxt.endScope() + class TestExpressionEngine(zope.tales.tests.TestCase): def setUp(self): @@ -183,6 +186,7 @@ class TestExpressionEngine(zope.tales.tests.TestCase): self.assertEqual(ctx.contexts['b'], 2) self.assertEqual(ctx.contexts['c'], 1) + class TestContext(unittest.TestCase): def setUp(self): @@ -249,7 +253,6 @@ class TestContext(unittest.TestCase): self.assertEqual(u'text', self.context.evaluateText("it")) def test_traceback_supplement(self): - import sys def raises(self): raise Exception() @@ -305,6 +308,7 @@ class Harness(object): def __getattr__(self, name): return HarnessMethod(self, name) + class HarnessMethod(object): def __init__(self, harness, name): @@ -333,11 +337,10 @@ class HarnessMethod(object): def test_suite(): - checker = renormalizing.RENormalizing( - [(re.compile(r"object of type 'MyIter' has no len\(\)"), - r"len() of unsized object"), - ] - ) + checker = renormalizing.RENormalizing([ + (re.compile(r"object of type 'MyIter' has no len\(\)"), + r"len() of unsized object"), + ]) suite = unittest.defaultTestLoader.loadTestsFromName(__name__) suite.addTest(DocTestSuite("zope.tales.tales", checker=checker)) diff --git a/src/zope/tales/tests/test_traverser.py b/src/zope/tales/tests/test_traverser.py index b791889..dd9eb0d 100644 --- a/src/zope/tales/tests/test_traverser.py +++ b/src/zope/tales/tests/test_traverser.py @@ -13,7 +13,7 @@ ############################################################################## """ Tests for zope.tales.expressions.simpleTraverse """ -from unittest import TestCase, TestSuite, makeSuite, main +from unittest import TestCase from zope.tales.expressions import simpleTraverse @@ -21,13 +21,16 @@ class AttrTraversable(object): """Traversable by attribute access""" attr = 'foo' + class ItemTraversable(object): """Traversable by item access""" + def __getitem__(self, name): if name == 'attr': return 'foo' raise KeyError(name) + class AllTraversable(AttrTraversable, ItemTraversable): """Traversable by attribute and item access""" pass @@ -35,6 +38,7 @@ class AllTraversable(AttrTraversable, ItemTraversable): _marker = object() + def getitem(ob, name, default=_marker): """Helper a la getattr(ob, name, default).""" try: @@ -82,7 +86,7 @@ class TraverserTests(TestCase): self.assertRaises(KeyError, getitem, ob, 'missing_attr') def testTraverseEmptyPath(self): - # simpleTraverse should return the original object if the path is emtpy + # simpleTraverse should return the original object if the path is empty ob = object() self.assertEqual(simpleTraverse(ob, [], None), ob) @@ -95,7 +99,8 @@ class TraverserTests(TestCase): # simpleTraverse should raise AttributeError ob = AttrTraversable() # Here lurks the bug (unexpected NamError raised) - self.assertRaises(AttributeError, simpleTraverse, ob, ['missing_attr'], None) + self.assertRaises( + AttributeError, simpleTraverse, ob, ['missing_attr'], None) def testTraverseByItem(self): # simpleTraverse should find attr through item access @@ -113,17 +118,7 @@ class TraverserTests(TestCase): self.assertEqual(simpleTraverse(ob, ['attr'], None), 'foo') def testTraverseByMissingAll(self): - # simpleTraverse should raise KeyError (because ob implements __getitem__) + # simpleTraverse should raise KeyError (because ob implements + # __getitem__) ob = AllTraversable() self.assertRaises(KeyError, simpleTraverse, ob, ['missing_attr'], None) - - -def test_suite(): - return TestSuite(( - makeSuite(TraverserTests), - )) - - -if __name__ == '__main__': - main(defaultTest='test_suite') - diff --git a/tox.ini b/tox.ini index 8929592..5413964 100644 --- a/tox.ini +++ b/tox.ini @@ -1,6 +1,6 @@ [tox] envlist = - py27,py35,py36,py37,py38,pypy,pypy3,coverage,docs + flake8,py27,py35,py36,py37,py38,pypy,pypy3,coverage,docs [testenv] commands = @@ -28,3 +28,9 @@ commands = deps = {[testenv]deps} .[docs] + + +[testenv:flake8] +deps = flake8 +skipinstall = true +commands = flake8 setup.py src -- cgit v1.2.1