
This allows hacking checks to be added as optional which means that they are assumed to be excluded and have to be explicitly added as part of a 'select' entry in the [flake8] stanza of tox.ini. The goal is to allow checks to be staged into the projects instead of requiring all of the projects to change to adhere to the check prior to the check being added (which creates a window where failing code could be added). Instead checks can be staged in with enforcement and once all (or the majority) of projects have enabled it, the check can be changed to a required check. One optional check is added as an example: H106: Don't put vim configuration in source files Depends-On: I7f8ef22254703a53b73ccd048322191cfc677958 Change-Id: If188f85e8093974802bd2bacc2a72b2ae35ee5a1
165 lines
4.5 KiB
Python
165 lines
4.5 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 pep8 checks using flake8.
|
|
"""
|
|
|
|
import gettext
|
|
import sys
|
|
|
|
import pbr.util
|
|
import pep8
|
|
|
|
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 pep8 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
|
|
|
|
|
|
class ProxyChecks(GlobalCheck):
|
|
"""Provide a mechanism for locally defined checks."""
|
|
name = 'ProxyChecker'
|
|
|
|
@classmethod
|
|
def add_options(cls, parser):
|
|
# We're looking for local checks, so we need to include the local
|
|
# dir in the search path
|
|
sys.path.append('.')
|
|
|
|
local_check = CONF.get_multiple('local-check', default=[])
|
|
for check_path in set(local_check):
|
|
if check_path.strip():
|
|
checker = pbr.util.resolve_name(check_path)
|
|
pep8.register_check(checker)
|
|
|
|
local_check_fact = CONF.get('local-check-factory')
|
|
if local_check_fact:
|
|
factory = pbr.util.resolve_name(local_check_fact)
|
|
factory(pep8.register_check)
|
|
|
|
sys.path.pop()
|