From 39acac4652e69b775c85709eda6accfb3470eae1 Mon Sep 17 00:00:00 2001 From: "753.network" Date: Tue, 14 Jan 2025 11:47:52 -0500 Subject: [PATCH] Add custom Flake8 plugin Create a new python project for flake8 plugins, currently only containing a plugin to test for unsafe usage of models with visibility --- Dockerfile | 1 + django/poetry.lock | 16 +++++- django/pyproject.toml | 1 + .../core/tests/test_coding_conventions.py | 53 ------------------- .../flake8_thunderstore/__init__.py | 1 + .../visibility_coding_conventions.py | 36 +++++++++++++ flake8-thunderstore/pyproject.toml | 18 +++++++ 7 files changed, 72 insertions(+), 54 deletions(-) delete mode 100644 django/thunderstore/core/tests/test_coding_conventions.py create mode 100644 flake8-thunderstore/flake8_thunderstore/__init__.py create mode 100644 flake8-thunderstore/flake8_thunderstore/visibility_coding_conventions.py create mode 100644 flake8-thunderstore/pyproject.toml diff --git a/Dockerfile b/Dockerfile index 5a2faa54a..d30a35f76 100644 --- a/Dockerfile +++ b/Dockerfile @@ -22,6 +22,7 @@ RUN apt-get update && apt-get install -y \ curl build-essential git \ && rm -rf /var/lib/apt/lists/* +COPY ./flake8-thunderstore/ /flake8-thunderstore COPY ./python-packages/ /python-packages COPY ./django/pyproject.toml ./django/poetry.lock /app/ diff --git a/django/poetry.lock b/django/poetry.lock index c7f972332..3c77ad308 100644 --- a/django/poetry.lock +++ b/django/poetry.lock @@ -1199,6 +1199,20 @@ files = [ [package.dependencies] flake8-plugin-utils = ">=1.3.1,<2.0.0" +[[package]] +name = "flake8-thunderstore" +version = "1.0.2" +description = "Custom Flake8 plugins for Thunderstore" +category = "dev" +optional = false +python-versions = "*" +files = [] +develop = true + +[package.source] +type = "directory" +url = "../flake8-thunderstore" + [[package]] name = "freezegun" version = "1.2.1" @@ -3136,4 +3150,4 @@ testing = ["coverage (>=5.0.3)", "zope.event", "zope.testing"] [metadata] lock-version = "2.0" python-versions = "^3.8" -content-hash = "623250fdf603ca415bb23c6a46c57bfa86499fb51c77b1ef0a6c6f1d74ef6e29" +content-hash = "74f60c439cb2cb3633591a4e7f5a573b80664da66a52d89db894d07e0eab1a69" diff --git a/django/pyproject.toml b/django/pyproject.toml index 0d43fcfbe..714274ff6 100644 --- a/django/pyproject.toml +++ b/django/pyproject.toml @@ -70,6 +70,7 @@ flake8-comprehensions = "^3.3.1" flake8-pie = "^0.6.1" flake8-printf-formatting = "^1.1.0" flake8-pytest-style = "^1.3.0" +flake8-thunderstore = { path = "../flake8-thunderstore", develop = true } pep8-naming = "^0.11.1" watchdog = {extras = ["watchmedo"], version = "^1.0.2"} mypy-boto3-s3 = "^1.17.47" diff --git a/django/thunderstore/core/tests/test_coding_conventions.py b/django/thunderstore/core/tests/test_coding_conventions.py deleted file mode 100644 index ccc63d186..000000000 --- a/django/thunderstore/core/tests/test_coding_conventions.py +++ /dev/null @@ -1,53 +0,0 @@ -import ast -import os - -import pytest - -visibility_classes = ["PackageListing", "PackageVersion"] - -safe_functions = ["public_list", "system", "get", "create", "get_or_create"] - -excluded_filepaths = ["/migrations/", "/tests/", "/commands/", "/conftest.py"] - - -def find_violations(tree, file_path): - violations = [] - - for node in ast.walk(tree): - if ( - isinstance(node, ast.Attribute) - and isinstance(node.value, ast.Attribute) - and node.value.attr == "objects" - and isinstance(node.value.value, ast.Name) - and node.value.value.id in visibility_classes - and node.attr not in safe_functions - ): - violations.append( - f"'{node.value.value.id}.objects.{node.attr}' at line {node.lineno} of '{file_path}'" - ) - return violations - - -@pytest.mark.django_db -def test_visibility_mixin_usage(): - violations = [] - - for root, _, files in os.walk("../app/"): - for file in files: - if file.endswith(".py"): - file_path = os.path.join(root, file) - if not any(excluded in file_path for excluded in excluded_filepaths): - with open(file_path, "r", encoding="utf-8") as f: - try: - tree = ast.parse(f.read(), filename=file_path) - - violations.extend(find_violations(tree, file_path)) - except SyntaxError as e: - pytest.fail(f"Syntax error in {file_path}: {e}") - - if violations: - pytest.fail( - "Found unsafe usage of visibility objects:\n" - + "\n".join(violations) - + "\nObjects with visibility should always be called with .public_list() or .system()" - ) diff --git a/flake8-thunderstore/flake8_thunderstore/__init__.py b/flake8-thunderstore/flake8_thunderstore/__init__.py new file mode 100644 index 000000000..4ecb0f839 --- /dev/null +++ b/flake8-thunderstore/flake8_thunderstore/__init__.py @@ -0,0 +1 @@ +from .visibility_coding_conventions import VisibilityCodingConventions diff --git a/flake8-thunderstore/flake8_thunderstore/visibility_coding_conventions.py b/flake8-thunderstore/flake8_thunderstore/visibility_coding_conventions.py new file mode 100644 index 000000000..5c6a459ae --- /dev/null +++ b/flake8-thunderstore/flake8_thunderstore/visibility_coding_conventions.py @@ -0,0 +1,36 @@ +import ast + +from flake8_plugin_utils import Plugin + + +class VisibilityCodingConventions(Plugin): + name = "visibility-coding-conventions" + version = "1.0.1" + + visibility_classes = ["PackageListing", "PackageVersion"] + safe_functions = ["public_list", "system", "get", "create", "get_or_create"] + excluded_filepaths = ["/migrations/", "/tests/", "/commands/", "/conftest.py"] + + def __init__(self, tree: ast.AST, filename: str) -> None: + self.tree = tree + self.filename = filename + + def run(self): + if any(excluded in self.filename for excluded in self.excluded_filepaths): + return + + for node in ast.walk(self.tree): + if ( + isinstance(node, ast.Attribute) + and isinstance(node.value, ast.Attribute) + and node.value.attr == "objects" + and isinstance(node.value.value, ast.Name) + and node.value.value.id in self.visibility_classes + and node.attr not in self.safe_functions + ): + yield ( + node.lineno, + node.col_offset, + f"VIS753 {node.value.value.id}.objects.{node.attr} is unsafe; Objects with visibility should always be called with .public_list() or .system())", + type(self), + ) diff --git a/flake8-thunderstore/pyproject.toml b/flake8-thunderstore/pyproject.toml new file mode 100644 index 000000000..99aab4364 --- /dev/null +++ b/flake8-thunderstore/pyproject.toml @@ -0,0 +1,18 @@ +[tool.poetry] +name = "flake8-thunderstore" +version = "1.0.2" +description = "Custom Flake8 plugins for Thunderstore" +authors = ["753 <753@753.network>"] +packages = [ + { include = "flake8_thunderstore" }, +] + +[tool.poetry.dependencies] +python = "*" + +[build-system] +requires = ["poetry-core>=1.0.0"] +build-backend = "poetry.core.masonry.api" + +[tool.poetry.plugins."flake8.extension"] +VIS = 'flake8_thunderstore.visibility_coding_conventions:VisibilityCodingConventions'