hacking/hacking/core.py
Stephen Finucane 172d34813d Bump flake8 version to something modern
The flake8 version we're using is a good 2 years old and is in turn
using an old version of pycodestyle. This is causing the following
warnings:

  FutureWarning: Possible nested set at position 1
    EXTRANEOUS_WHITESPACE_REGEX = re.compile(r'[[({] | []}),;:]')

Bump to the latest and greatest so we get these fixes. This involves
dropping support for local-checks, which don't seem to work in this
version. Fortunately, in the intervening time, flake8 has grown the
ability to do local checks all by its lonesome. The tests also need to
be reworked and unfortunately are much slower now. This is because
flake8 3.x's API is file-based [1] and without rooting around the guts
of flake8, it's not practical to do things any other way. Hopefully
flake8 4.x won't have such issues.

[1] https://gitlab.com/pycqa/flake8/issues/545

Change-Id: I695ff02a6970663add10caf7f16a66abf9d1239d
Signed-off-by: Stephen Finucane <sfinucan@redhat.com>
Sem-Ver: api-break
2019-12-11 17:18:21 +00:00

138 lines
3.8 KiB
Python

# Copyright (c) 2012, Cloudscaling
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""OpenStack HACKING file compliance testing
Built as a sets of pycodestyle checks using flake8.
"""
import gettext
import sys
from hacking import config
# Import tests need to inject _ properly into the builtins
kwargs = {}
if sys.version_info[0] < 3:
# In Python2, ensure that the _() that gets installed into built-ins
# always returns unicodes. This matches the default behavior under Python
# 3, although the keyword argument is not present in the Python 3 API.
kwargs['unicode'] = True
gettext.install('hacking', **kwargs)
def flake8ext(f):
f.name = __name__
f.version = '0.0.1'
f.skip_on_py3 = False
if not hasattr(f, 'off_by_default'):
f.off_by_default = False
return f
def off_by_default(f):
"""Decorator to turn check off by default.
To enable the check use the flake8 select setting in
tox.ini.
flake8 documentation:
http://flake8.readthedocs.org/en/latest/extensions.html.
"""
f.off_by_default = True
return f
def skip_on_py3(f):
f.skip_on_py3 = True
return f
# Error code block layout
# H1xx comments
# H20x except
# H23x Python 2.x -> 3.x portability issues
# H3xx imports
# H4xx docstrings
# H5xx dictionaries/lists
# H6xx calling methods
# H7xx localization
# H8xx git commit messages
# H9xx other
CONF = config.Config('hacking')
DEFAULT_IMPORT_EXCEPTIONS = [
'sqlalchemy',
'migrate',
]
IMPORT_EXCEPTIONS = CONF.get_multiple('import_exceptions', default=[])
IMPORT_EXCEPTIONS += DEFAULT_IMPORT_EXCEPTIONS
def is_import_exception(mod):
"""Check module name to see if import has been whitelisted.
Import based rules should not run on any whitelisted module
"""
return (mod in IMPORT_EXCEPTIONS or
any(mod.startswith(m + '.') for m in IMPORT_EXCEPTIONS))
def import_normalize(line):
# convert "from x import y" to "import x.y"
# handle "from x import y as z" to "import x.y as z"
split_line = line.split()
if ("import" in line and line.startswith("from ") and "," not in line and
split_line[2] == "import" and split_line[3] != "*" and
split_line[1] != "__future__" and
(len(split_line) == 4 or
(len(split_line) == 6 and split_line[4] == "as"))):
return "import %s.%s" % (split_line[1], split_line[3])
else:
return line
class GlobalCheck(object):
"""Base class for checks that should be run only once."""
name = None
version = '0.0.1'
skip_on_py3 = False
_has_run = set()
def __init__(self, tree, *args):
pass
def run(self):
"""Make run a no-op if run() has been called before.
Store in a global registry the list of checks we've run. If we have
run that one before, just skip doing anything the subsequent times.
This way, since pycodestyle is file/line based, we don't wind
up re-running a check on a git commit message over and over again.
"""
if self.name and self.name not in self.__class__._has_run:
self.__class__._has_run.add(self.name)
ret = self.run_once()
if ret is not None:
yield ret
def run_once(self):
pass