From b13799d58443f1164d5f28330b2c5636a810bb13 Mon Sep 17 00:00:00 2001 From: CaptainB Date: Wed, 14 May 2025 14:35:13 +0800 Subject: [PATCH] feat: add Pylint API for code checking and integrate with tool operations --- apps/tools/api/tool.py | 28 +++++++++++++++++++++- apps/tools/serializers/tool.py | 44 +++++++++++++++++++++++++++++++++- apps/tools/urls.py | 1 + apps/tools/views/tool.py | 21 +++++++++++++++- 4 files changed, 91 insertions(+), 3 deletions(-) diff --git a/apps/tools/api/tool.py b/apps/tools/api/tool.py index 049090ad1..3ffcb9983 100644 --- a/apps/tools/api/tool.py +++ b/apps/tools/api/tool.py @@ -4,7 +4,8 @@ from drf_spectacular.utils import OpenApiParameter from common.mixins.api_mixin import APIMixin from common.result import ResultSerializer, DefaultResultSerializer -from tools.serializers.tool import ToolModelSerializer, ToolCreateRequest, ToolDebugRequest, ToolEditRequest +from tools.serializers.tool import ToolModelSerializer, ToolCreateRequest, ToolDebugRequest, ToolEditRequest, \ + PylintInstance class ToolCreateResponse(ResultSerializer): @@ -209,3 +210,28 @@ class ToolPageAPI(ToolReadAPI): required=False, ), ] + + +class PylintAPI(APIMixin): + @staticmethod + def get_parameters(): + return [ + OpenApiParameter( + name="workspace_id", + description="工作空间id", + type=OpenApiTypes.STR, + location='path', + required=True, + ), + OpenApiParameter( + name="tool_id", + description="工具id", + type=OpenApiTypes.STR, + location='path', + required=True, + ) + ] + + @staticmethod + def get_request(): + return PylintInstance diff --git a/apps/tools/serializers/tool.py b/apps/tools/serializers/tool.py index e3bcf0bcc..11c58d804 100644 --- a/apps/tools/serializers/tool.py +++ b/apps/tools/serializers/tool.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- import io import json +import os import pickle import re @@ -10,13 +11,15 @@ from django.db import transaction from django.db.models import QuerySet, Q from django.http import HttpResponse from django.utils.translation import gettext_lazy as _ +from pylint.lint import Run +from pylint.reporters import JSON2Reporter from rest_framework import serializers, status from common.db.search import page_search from common.exception.app_exception import AppApiException from common.result import result from common.utils.tool_code import ToolExecutor -from maxkb.const import CONFIG +from maxkb.const import CONFIG, PROJECT_DIR from tools.models import Tool, ToolScope, ToolFolder from tools.serializers.tool_folder import ToolFolderFlatSerializer @@ -36,6 +39,25 @@ ALLOWED_CLASSES = { } +def to_dict(message, file_name): + return { + 'line': message.line, + 'column': message.column, + 'endLine': message.end_line, + 'endColumn': message.end_column, + 'message': (message.msg or "").replace(file_name, 'code'), + 'type': message.category + } + + +def get_file_name(): + file_name = f"{uuid.uuid7()}" + pylint_dir = os.path.join(PROJECT_DIR, 'data', 'pylint') + if not os.path.exists(pylint_dir): + os.makedirs(pylint_dir) + return os.path.join(pylint_dir, file_name) + + class RestrictedUnpickler(pickle.Unpickler): def find_class(self, folder, name): @@ -164,6 +186,10 @@ class ToolDebugRequest(serializers.Serializer): debug_field_list = DebugField(required=True, many=True) +class PylintInstance(serializers.Serializer): + code = serializers.CharField(required=True, allow_null=True, allow_blank=True, label=_('function content')) + + class ToolSerializer(serializers.Serializer): class Create(serializers.Serializer): user_id = serializers.UUIDField(required=True, label=_('user id')) @@ -287,6 +313,22 @@ class ToolSerializer(serializers.Serializer): except Exception as e: return result.error(str(e), response_status=status.HTTP_500_INTERNAL_SERVER_ERROR) + def pylint(self, instance, is_valid=True): + if is_valid: + self.is_valid(raise_exception=True) + PylintInstance(data=instance).is_valid(raise_exception=True) + code = instance.get('code') + file_name = get_file_name() + with open(file_name, 'w') as file: + file.write(code) + reporter = JSON2Reporter() + Run([file_name, + "--disable=line-too-long", + '--module-rgx=[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}'], + reporter=reporter, exit=False) + os.remove(file_name) + return [to_dict(m, os.path.basename(file_name)) for m in reporter.messages] + class Import(serializers.Serializer): file = UploadedFileField(required=True, label=_("file")) user_id = serializers.UUIDField(required=True, label=_("User ID")) diff --git a/apps/tools/urls.py b/apps/tools/urls.py index 542de2e61..1fa3b18f3 100644 --- a/apps/tools/urls.py +++ b/apps/tools/urls.py @@ -8,6 +8,7 @@ urlpatterns = [ path('workspace//tool/import', views.ToolView.Import.as_view()), path('workspace//tool/', views.ToolView.Operate.as_view()), path('workspace//tool//debug', views.ToolView.Debug.as_view()), + path('workspace//tool//pylint', views.ToolView.Pylint.as_view()), path('workspace//tool//export', views.ToolView.Export.as_view()), path('workspace//tool//', views.ToolView.Page.as_view()), ] diff --git a/apps/tools/views/tool.py b/apps/tools/views/tool.py index afaa9c271..988e8b7ce 100644 --- a/apps/tools/views/tool.py +++ b/apps/tools/views/tool.py @@ -9,7 +9,7 @@ from common.auth.authentication import has_permissions from common.constants.permission_constants import PermissionConstants from common.result import result from tools.api.tool import ToolCreateAPI, ToolEditAPI, ToolReadAPI, ToolDeleteAPI, ToolTreeReadAPI, ToolDebugApi, \ - ToolExportAPI, ToolImportAPI, ToolPageAPI + ToolExportAPI, ToolImportAPI, ToolPageAPI, PylintAPI from tools.serializers.tool import ToolSerializer, ToolTreeSerializer @@ -174,3 +174,22 @@ class ToolView(APIView): return ToolSerializer.Operate( data={'id': tool_id, 'workspace_id': workspace_id} ).export() + + class Pylint(APIView): + authentication_classes = [TokenAuth] + + @extend_schema( + methods=['POST'], + summary=_('Check code'), + operation_id=_('Check code'), + description=_('Check code'), + request=PylintAPI.get_request(), + responses=PylintAPI.get_response(), + parameters=PylintAPI.get_parameters(), + tags=[_('Tool')] + ) + @has_permissions(PermissionConstants.TOOL_EXPORT.get_workspace_permission()) + def post(self, request: Request, workspace_id: str, tool_id: str): + return result.success(ToolSerializer.Operate( + data={'id': tool_id, 'workspace_id': workspace_id} + ).pylint(request.data))