From fa9bfb7b3a37e0e981a0006ae7bf3ec7781b6afb Mon Sep 17 00:00:00 2001 From: "753.network" Date: Thu, 9 Jan 2025 11:41:14 -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/plugins/__init__.py | 1 + .../plugins/visibility_coding_conventions.py | 36 +++++++++++++ flake8_thunderstore/setup.py | 14 +++++ python-packages | 2 +- 8 files changed, 69 insertions(+), 55 deletions(-) delete mode 100644 django/thunderstore/core/tests/test_coding_conventions.py create mode 100644 flake8_thunderstore/plugins/__init__.py create mode 100644 flake8_thunderstore/plugins/visibility_coding_conventions.py create mode 100644 flake8_thunderstore/setup.py diff --git a/Dockerfile b/Dockerfile index 5a2faa54a..2cd1cd5e1 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..2f25d764f 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.0" +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 = "7da456655a24abc724849e41fa1af7e6b651e0fe85b31500326823668d7d2566" diff --git a/django/pyproject.toml b/django/pyproject.toml index 0d43fcfbe..4a135f3dd 100644 --- a/django/pyproject.toml +++ b/django/pyproject.toml @@ -44,6 +44,7 @@ django-paypal = { path = "../python-packages/django-paypal", develop = true } paypal-api = { path = "../python-packages/paypal-api", develop = true } ts-subsys = { path = "../python-packages/ts-subsys", develop = true } ts-scanners = { path = "../python-packages/ts-scanners", develop = true } +flake8-thunderstore = { path = "../flake8_thunderstore", develop = true } [tool.poetry.dev-dependencies] pytest = "^6.2" 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/plugins/__init__.py b/flake8_thunderstore/plugins/__init__.py new file mode 100644 index 000000000..4ecb0f839 --- /dev/null +++ b/flake8_thunderstore/plugins/__init__.py @@ -0,0 +1 @@ +from .visibility_coding_conventions import VisibilityCodingConventions diff --git a/flake8_thunderstore/plugins/visibility_coding_conventions.py b/flake8_thunderstore/plugins/visibility_coding_conventions.py new file mode 100644 index 000000000..f6c4e452f --- /dev/null +++ b/flake8_thunderstore/plugins/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.3" + + 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/setup.py b/flake8_thunderstore/setup.py new file mode 100644 index 000000000..152437c78 --- /dev/null +++ b/flake8_thunderstore/setup.py @@ -0,0 +1,14 @@ +from setuptools import find_packages, setup + +setup( + name="flake8-thunderstore", + version="1.0.0", + description="Custom Flake8 plugins for Thunderstore", + author="Your Name", + packages=find_packages(), + entry_points={ + "flake8.extension": [ + "VIS753 = plugins:VisibilityCodingConventions", + ], + }, +) diff --git a/python-packages b/python-packages index f467459be..bc6e49432 160000 --- a/python-packages +++ b/python-packages @@ -1 +1 @@ -Subproject commit f467459be94cfcba519b0cb9ab3e22896c913a82 +Subproject commit bc6e49432058228a9f1f69c77a9e5762b9e8c1d5