约定不仅是代码样式指南。如果包含一些规则以确保安全性,则为审阅者添加一些工作。使用一些代码自动进行此检查可能很有趣。
Flake8是确保您的代码遵守某些规则的工具。您可以添加插件,并且很容易创建自己的插件。这篇文章是AnthonyWritesCode的this very good Youtube tutorial的文本版本。
非Syntax惯例
您可以使用pylint或黑色重新格式化和统一您的代码,但会议可能包含其他一些规则:
-
安全性:您的公司可能禁止使用
os.popen()
或禁止在没有cert
属性的request.get()
的功能。 -
日志消息:您可能有一些规则具有显式和描述性日志条目,例如
logger.critical()
必须包含一些特定的关键字(错误uid,跟踪跨度...)。
自动化您的约定检查
构建Flake8具有两个主要优点:
- 审查清单越来越薄,因为持续的集成工具将完成这项工作。
- 开发人员可以通过这种违规行为提醒并修复自己而不是等待成对审查。
如果您要禁止在没有文档的情况下添加控制器类的添加,则只需确保设置文档:审阅者检测空的DocString比每个Controler类都包含文档更容易。
自动化检查的第一部分(存在),以帮助审阅者专注于验证内容。因此,某些规则只能是部分自动化的,但仍会减少审阅者的心理负担。
遵循您的技术债务
如果您有很多项目,并且决定从图书馆A迁移到图书馆B,则可以使用Flake8提醒您留下库的使用。
设置了 internal_conventions 插件并运行,很容易使用一些新的错误代码添加一些规则。
创建Flake8插件
使用Flake8插件检查自己的规则非常容易(不仅是因为我们不必使用Regex),因此Bootstrap很小。
在此示例中,我编写一个插件来检查logger.info()
的每个呼叫都包含mandatory_arg
。因此,logger.info("abc", mandatory_arg=1)
有效,而logger.info("abc")
无效。
我的项目名称是flake8_check_logger
,flake8_
是Flake8插件的前缀(这个良好的命名约定帮助我们在Github上发现插件)。我们使用错误代码 log042 和消息“无效的记录器”。顺便说一句,使用Flake8插件尚未使用的前缀。
设置
setup.py:
from setuptools import setup
setup()
setup.cfg:
[metadata]
name = flake8_check_logger
version = 0.1.0
[options]
py_modules = flake8_check_logger
install_requires =
flake8>=3.7
[options.entry_points]
flake8.extension =
LOG=flake8_check_logger:Plugin
flake8_check_logger.py
import importlib.metadata
import ast
from typing import Any, Generator, Type, Tuple
class Visitor(ast.NodeVisitor):
def __init__(self):
self.problems = []
def visit_Call(self, node: ast.Call) -> None:
# TODO we write our test here
self.generic_visit(node)
class Plugin:
name = __name__
version = importlib.metadata.version(__name__)
def __init__(self, tree: ast.AST):
self._tree = tree
def run(self) -> Generator[Tuple[int, int, str, Type[Any]], None, None]:
visitor = Visitor()
visitor.visit(self._tree)
for line, col, code, message in visitor.problems:
yield line, col, f"LOG{code} {message}", type(self)
发现ast(和astpretty)
而不是使用Regex,我们将使用抽象的语法树。 AST将任何Python源代码转换为对象表示。
我们将仅用于开发目的的PIP安装astpretty,这不是添加我们的插件的要求。
然后您可以在终端上使用它:
astpretty /dev/stdin <<< "logger.info('msg', mandatory_arg=1)"
我们获得了一些信息,例如
Expr(
value=Call(
func=Attribute(
value=Name(id='logger', ctx=Load()),
attr='info',
ctx=Load(),
),
args=[Constant(value='msg', kind=None)],
keywords=[
keyword(
arg='mandatory_arg',
value=Constant(value=1, kind=None),
),
],
),
),
写测试
from flake8_check_logger import Plugin
import ast
from typing import Set
def _results(source: str) -> Set[str]:
tree = ast.parse(source)
plugin = Plugin(tree)
return {f"{line}:{col} {msg}" for line, col, msg, _ in plugin.run()}
def test_valid_logger_call():
assert _results("logger.info('msg', mandatory_arg=1)") == set()
def test_invalid_logger_call_no_mandatory_arg():
ret = _results("logger.info('msg')")
assert ret == {'1:0 LOG042 Invalid Logger'}
您可以添加更多测试柜:logger.something_else()
或具有类似名称的本地功能info()
不需要此参数,测试一个空行,等等。
写支票
我将测试分为两个部分,以实现可恶之处,可以重新制定该代码,但我更喜欢在本教程中使用非常基本的语法。
def visit_Call(self, node: ast.Call) -> None:
mandatory_arg_found = False
is_right_function = False
if hasattr(node.func, "value") and node.func.value.id == "logger":
if node.func.attr == "info":
is_right_function = True
if is_right_function:
for keyword in node.keywords:
if keyword.arg == "mandatory_arg" and keyword.value.value != None:
mandatory_arg_found = True
if not mandatory_arg_found:
self.problems.append((
node.lineno,
node.col_offset,
042,
"Invalid logger"
))
self.generic_visit(node)
本地测试
我们使用:
安装插件
pip install -e .
。
,您可以使用
检查Flake Load此插件
flake8 --version
。
走得更远
您可以在flake8 documentation上找到更多信息,并具有非常有用的视频教程(30分钟)。
使用Python,您可以使用代码:__doc__
用于文档或__bases__
的代码。这些dunder功能可以帮助您编写一些特定的规则。
,您可以通过Jonathan Bowman article了解有关现有的Flake8插件的更多信息,并阅读这些插件的源代码,以帮助您如果您被复杂的情况阻止。