feat: add Pylint API for code checking and integrate with tool operations

This commit is contained in:
CaptainB 2025-05-14 14:35:13 +08:00
parent d85d20694d
commit b13799d584
4 changed files with 91 additions and 3 deletions

View File

@ -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

View File

@ -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"))

View File

@ -8,6 +8,7 @@ urlpatterns = [
path('workspace/<str:workspace_id>/tool/import', views.ToolView.Import.as_view()),
path('workspace/<str:workspace_id>/tool/<str:tool_id>', views.ToolView.Operate.as_view()),
path('workspace/<str:workspace_id>/tool/<str:tool_id>/debug', views.ToolView.Debug.as_view()),
path('workspace/<str:workspace_id>/tool/<str:tool_id>/pylint', views.ToolView.Pylint.as_view()),
path('workspace/<str:workspace_id>/tool/<str:tool_id>/export', views.ToolView.Export.as_view()),
path('workspace/<str:workspace_id>/tool/<int:current_page>/<int:page_size>', views.ToolView.Page.as_view()),
]

View File

@ -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))