1import re 2import py 3import pytest 4import pep8 5import os 6 7__version__ = '1.0.6' 8 9HISTKEY = "pep8/mtimes" 10 11 12def pytest_addoption(parser): 13 group = parser.getgroup("general") 14 group.addoption('--pep8', action='store_true', 15 help="perform some pep8 sanity checks on .py files") 16 parser.addini("pep8ignore", type="linelist", 17 help="each line specifies a glob pattern and whitespace " 18 "separated PEP8 errors or warnings which will be ignored, " 19 "example: *.py W293") 20 parser.addini("pep8maxlinelength", 21 help="max. line length (default: %d)" % pep8.MAX_LINE_LENGTH) 22 23 24def pytest_sessionstart(session): 25 config = session.config 26 if config.option.pep8: 27 config._pep8ignore = Ignorer(config.getini("pep8ignore")) 28 config._pep8mtimes = config.cache.get(HISTKEY, {}) 29 config._max_line_length = int(config.getini("pep8maxlinelength") 30 or pep8.MAX_LINE_LENGTH) 31 32 33def pytest_collect_file(path, parent): 34 config = parent.config 35 if config.option.pep8 and path.ext == '.py': 36 pep8ignore = config._pep8ignore(path) 37 if pep8ignore is not None: 38 return Pep8Item(path, parent, pep8ignore, config._max_line_length) 39 40 41def pytest_sessionfinish(session): 42 config = session.config 43 if hasattr(config, "_pep8mtimes"): 44 config.cache.set(HISTKEY, config._pep8mtimes) 45 46 47class Pep8Error(Exception): 48 """ indicates an error during pep8 checks. """ 49 50 51class Pep8Item(pytest.Item, pytest.File): 52 53 def __init__(self, path, parent, pep8ignore, max_line_length): 54 super(Pep8Item, self).__init__(path, parent) 55 self.add_marker("pep8") 56 self.pep8ignore = pep8ignore 57 self.max_line_length = max_line_length 58 59 def setup(self): 60 pep8mtimes = self.config._pep8mtimes 61 self._pep8mtime = self.fspath.mtime() 62 old = pep8mtimes.get(str(self.fspath), (0, [])) 63 if old == (self._pep8mtime, self.pep8ignore): 64 pytest.skip("file(s) previously passed PEP8 checks") 65 66 def runtest(self): 67 call = py.io.StdCapture.call 68 found_errors, out, err = call(check_file, self.fspath, self.pep8ignore, 69 self.max_line_length) 70 if found_errors: 71 raise Pep8Error(out, err) 72 # update mtime only if test passed 73 # otherwise failures would not be re-run next time 74 self.config._pep8mtimes[str(self.fspath)] = (self._pep8mtime, 75 self.pep8ignore) 76 77 def repr_failure(self, excinfo): 78 if excinfo.errisinstance(Pep8Error): 79 return excinfo.value.args[0] 80 return super(Pep8Item, self).repr_failure(excinfo) 81 82 def reportinfo(self): 83 if self.pep8ignore: 84 ignores = "(ignoring %s)" % " ".join(self.pep8ignore) 85 else: 86 ignores = "" 87 return (self.fspath, -1, "PEP8-check%s" % ignores) 88 89 90class Ignorer: 91 def __init__(self, ignorelines, coderex=re.compile("[EW]\d\d\d")): 92 self.ignores = ignores = [] 93 for line in ignorelines: 94 i = line.find("#") 95 if i != -1: 96 line = line[:i] 97 try: 98 glob, ign = line.split(None, 1) 99 except ValueError: 100 glob, ign = None, line 101 if glob and coderex.match(glob): 102 glob, ign = None, line 103 ign = ign.split() 104 if "ALL" in ign: 105 ign = None 106 if glob and "/" != os.sep and "/" in glob: 107 glob = glob.replace("/", os.sep) 108 ignores.append((glob, ign)) 109 110 def __call__(self, path): 111 l = [] 112 for (glob, ignlist) in self.ignores: 113 if not glob or path.fnmatch(glob): 114 if ignlist is None: 115 return None 116 l.extend(ignlist) 117 return l 118 119 120def check_file(path, pep8ignore, max_line_length): 121 checker = pep8.Checker(str(path), ignore=pep8ignore, show_source=1, 122 max_line_length=max_line_length) 123 return checker.check_all() 124