feat: 支持函数库脚本代码校验

This commit is contained in:
shaohuzhang1 2024-10-08 16:58:46 +08:00 committed by shaohuzhang1
parent 7070334e86
commit 3a7e9a3cea
13 changed files with 185 additions and 38 deletions

View File

@ -0,0 +1,57 @@
# coding=utf-8
"""
@project: MaxKB
@Author
@file py_lint_serializer.py
@date2024/9/30 15:38
@desc:
"""
import os
import uuid
from pylint.lint import Run
from pylint.reporters import JSON2Reporter
from rest_framework import serializers
from common.util.field_message import ErrMessage
from smartdoc.const import PROJECT_DIR
class PyLintInstance(serializers.Serializer):
code = serializers.CharField(required=True, error_messages=ErrMessage.char("函数名称"))
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.uuid1()}"
py_lint_dir = os.path.join(PROJECT_DIR, 'data', 'py_lint')
if not os.path.exists(py_lint_dir):
os.makedirs(py_lint_dir)
return os.path.join(py_lint_dir, file_name)
class PyLintSerializer(serializers.Serializer):
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]

View File

@ -0,0 +1,23 @@
# coding=utf-8
"""
@project: MaxKB
@Author
@file py_lint_api.py
@date2024/9/30 15:48
@desc:
"""
from drf_yasg import openapi
from common.mixins.api_mixin import ApiMixin
class PyLintApi(ApiMixin):
@staticmethod
def get_request_body_api():
return openapi.Schema(
type=openapi.TYPE_OBJECT,
required=['code'],
properties={
'code': openapi.Schema(type=openapi.TYPE_STRING, title="函数内容", description="函数内容")
}
)

View File

@ -6,7 +6,8 @@ app_name = "function_lib"
urlpatterns = [
path('function_lib', views.FunctionLibView.as_view()),
path('function_lib/debug', views.FunctionLibView.Debug.as_view()),
path('function_lib/pylint', views.PyLintView.as_view()),
path('function_lib/<str:function_lib_id>', views.FunctionLibView.Operate.as_view()),
path("function_lib/<int:current_page>/<int:page_size>", views.FunctionLibView.Page.as_view(),
name="function_lib_page"),
name="function_lib_page")
]

View File

@ -7,3 +7,4 @@
@desc:
"""
from .function_lib_views import *
from .py_lint import *

View File

@ -0,0 +1,31 @@
# coding=utf-8
"""
@project: MaxKB
@Author
@file py_lint.py
@date2024/9/30 15:35
@desc:
"""
from drf_yasg.utils import swagger_auto_schema
from rest_framework.decorators import action
from rest_framework.request import Request
from rest_framework.views import APIView
from common.auth import TokenAuth, has_permissions
from common.constants.permission_constants import RoleConstants
from common.response import result
from function_lib.serializers.py_lint_serializer import PyLintSerializer
from function_lib.swagger_api.py_lint_api import PyLintApi
class PyLintView(APIView):
authentication_classes = [TokenAuth]
@action(methods=['POST'], detail=False)
@swagger_auto_schema(operation_summary="校验代码",
operation_id="校验代码",
request_body=PyLintApi.get_request_body_api(),
tags=['函数库'])
@has_permissions(RoleConstants.ADMIN, RoleConstants.USER)
def post(self, request: Request):
return result.success(PyLintSerializer(data={'user_id': request.user.id}).pylint(request.data))

View File

@ -53,6 +53,7 @@ celery = { extras = ["sqlalchemy"], version = "^5.4.0" }
django-celery-beat = "^2.6.0"
celery-once = "^3.0.1"
anthropic = "^0.34.2"
pylint = "3.1.0"
[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"

View File

@ -13,6 +13,7 @@
"format": "prettier --write src/"
},
"dependencies": {
"@codemirror/theme-one-dark": "^6.1.2",
"@ctrl/tinycolor": "^4.1.0",
"@logicflow/core": "^1.2.27",
"@logicflow/extension": "^1.2.27",
@ -37,14 +38,14 @@
"nprogress": "^0.2.0",
"pinia": "^2.1.6",
"pinyin-pro": "^3.18.2",
"recorder-core": "^1.3.24040900",
"screenfull": "^6.0.2",
"use-element-plus-theme": "^0.0.5",
"vue": "^3.3.4",
"vue-clipboard3": "^2.0.0",
"vue-codemirror": "^6.1.1",
"vue-i18n": "^9.13.1",
"vue-router": "^4.2.4",
"recorder-core": "^1.3.24040900"
"vue-router": "^4.2.4"
},
"devDependencies": {
"@rushstack/eslint-patch": "^1.3.2",

View File

@ -95,6 +95,9 @@ const getFunctionLibById: (
) => Promise<Result<any>> = (function_lib_id, loading) => {
return get(`${prefix}/${function_lib_id}`, undefined, loading)
}
const pylint: (code: string, loading?: Ref<boolean>) => Promise<Result<any>> = (code, loading) => {
return post(`${prefix}/pylint`, { code }, {}, loading)
}
export default {
getFunctionLib,
@ -103,5 +106,6 @@ export default {
postFunctionLibDebug,
getAllFunctionLib,
delFunctionLib,
getFunctionLibById
getFunctionLibById,
pylint
}

View File

@ -1,27 +1,55 @@
<template>
<Codemirror v-bind="$attrs" border :options="cmOption" ref="cmRef" :style="codemirrorStyle" />
<Codemirror
v-bind="$attrs"
ref="cmRef"
:extensions="extensions"
:style="codemirrorStyle"
:tab-size="4"
:autofocus="true"
/>
</template>
<script setup lang="ts">
import { ref, onMounted, onUnmounted } from 'vue'
import { ref } from 'vue'
import { Codemirror } from 'vue-codemirror'
import { python } from '@codemirror/lang-python'
import { oneDark } from '@codemirror/theme-one-dark'
import { linter, Diagnostic } from '@codemirror/lint'
import FunctionApi from '@/api/function-lib'
defineOptions({ name: 'CodemirrorEditor' })
const cmOption = {
tabSize: 4,
lineWrapping: true,
lineNumbers: true,
line: true
function getRangeFromLineAndColumn(state: any, line: number, column: number, end_column?: number) {
const l = state.doc.line(line)
return { form: l.from + column, to: end_column ? l.from + end_column : l.to }
}
const regexpLinter = linter(async (view) => {
let diagnostics: Diagnostic[] = []
await FunctionApi.pylint(view.state.doc.toString()).then((ok) => {
ok.data.forEach((element: any) => {
const range = getRangeFromLineAndColumn(
view.state,
element.line,
element.column,
element.endColumn
)
diagnostics.push({
from: range.form,
to: range.to,
severity: element.type,
message: element.message
})
})
})
return diagnostics
})
const extensions = [python(), regexpLinter, oneDark]
const codemirrorStyle = {
height: '210px!important'
}
const cmRef = ref()
onMounted(() => {})
onUnmounted(() => {
cmRef.value?.destroy()
})
const cmRef = ref<InstanceType<typeof Codemirror>>()
</script>
<style lang="scss"></style>

View File

@ -721,24 +721,6 @@ h5 {
background: var(--el-color-primary-light-9) !important;
}
// Codemirror 编辑器
.function-CodemirrorEditor {
border: 1px solid #bbbfc4;
border-radius: 4px;
position: relative;
padding-bottom: 20px;
&__footer {
.magnify {
position: absolute;
bottom: 5px;
right: 5px;
}
}
.cm-gutters {
display: none !important;
}
}
.edit-avatar {
position: relative;
.edit-mask {

View File

@ -9,7 +9,7 @@
--app-view-padding: 24px;
--app-view-bg-color: #ffffff;
--app-border-color-dark: #bbbfc4;
--md-bk-hover-color:var(--el-border-color-hover);
/** header 组件 */
--app-header-height: 56px;
--app-header-padding: 0 20px;

View File

@ -305,4 +305,13 @@ defineExpose({
open
})
</script>
<style lang="scss" scoped></style>
<style lang="scss" scoped>
.function-CodemirrorEditor__footer {
position: absolute;
bottom: 10px;
right: 10px;
}
.function-CodemirrorEditor {
position: relative;
}
</style>

View File

@ -221,4 +221,13 @@ onMounted(() => {
}, 100)
})
</script>
<style lang="scss"></style>
<style lang="scss">
.function-CodemirrorEditor__footer {
position: absolute;
bottom: 10px;
right: 10px;
}
.function-CodemirrorEditor {
position: relative;
}
</style>