feat: 支持函数库脚本代码校验
parent
7070334e86
commit
3a7e9a3cea
|
|
@ -0,0 +1,57 @@
|
||||||
|
# coding=utf-8
|
||||||
|
"""
|
||||||
|
@project: MaxKB
|
||||||
|
@Author:虎
|
||||||
|
@file: py_lint_serializer.py
|
||||||
|
@date:2024/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]
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
# coding=utf-8
|
||||||
|
"""
|
||||||
|
@project: MaxKB
|
||||||
|
@Author:虎
|
||||||
|
@file: py_lint_api.py
|
||||||
|
@date:2024/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="函数内容")
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
@ -6,7 +6,8 @@ app_name = "function_lib"
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path('function_lib', views.FunctionLibView.as_view()),
|
path('function_lib', views.FunctionLibView.as_view()),
|
||||||
path('function_lib/debug', views.FunctionLibView.Debug.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/<str:function_lib_id>', views.FunctionLibView.Operate.as_view()),
|
||||||
path("function_lib/<int:current_page>/<int:page_size>", views.FunctionLibView.Page.as_view(),
|
path("function_lib/<int:current_page>/<int:page_size>", views.FunctionLibView.Page.as_view(),
|
||||||
name="function_lib_page"),
|
name="function_lib_page")
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -7,3 +7,4 @@
|
||||||
@desc:
|
@desc:
|
||||||
"""
|
"""
|
||||||
from .function_lib_views import *
|
from .function_lib_views import *
|
||||||
|
from .py_lint import *
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,31 @@
|
||||||
|
# coding=utf-8
|
||||||
|
"""
|
||||||
|
@project: MaxKB
|
||||||
|
@Author:虎
|
||||||
|
@file: py_lint.py
|
||||||
|
@date:2024/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))
|
||||||
|
|
@ -53,6 +53,7 @@ celery = { extras = ["sqlalchemy"], version = "^5.4.0" }
|
||||||
django-celery-beat = "^2.6.0"
|
django-celery-beat = "^2.6.0"
|
||||||
celery-once = "^3.0.1"
|
celery-once = "^3.0.1"
|
||||||
anthropic = "^0.34.2"
|
anthropic = "^0.34.2"
|
||||||
|
pylint = "3.1.0"
|
||||||
[build-system]
|
[build-system]
|
||||||
requires = ["poetry-core"]
|
requires = ["poetry-core"]
|
||||||
build-backend = "poetry.core.masonry.api"
|
build-backend = "poetry.core.masonry.api"
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@
|
||||||
"format": "prettier --write src/"
|
"format": "prettier --write src/"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@codemirror/theme-one-dark": "^6.1.2",
|
||||||
"@ctrl/tinycolor": "^4.1.0",
|
"@ctrl/tinycolor": "^4.1.0",
|
||||||
"@logicflow/core": "^1.2.27",
|
"@logicflow/core": "^1.2.27",
|
||||||
"@logicflow/extension": "^1.2.27",
|
"@logicflow/extension": "^1.2.27",
|
||||||
|
|
@ -37,14 +38,14 @@
|
||||||
"nprogress": "^0.2.0",
|
"nprogress": "^0.2.0",
|
||||||
"pinia": "^2.1.6",
|
"pinia": "^2.1.6",
|
||||||
"pinyin-pro": "^3.18.2",
|
"pinyin-pro": "^3.18.2",
|
||||||
|
"recorder-core": "^1.3.24040900",
|
||||||
"screenfull": "^6.0.2",
|
"screenfull": "^6.0.2",
|
||||||
"use-element-plus-theme": "^0.0.5",
|
"use-element-plus-theme": "^0.0.5",
|
||||||
"vue": "^3.3.4",
|
"vue": "^3.3.4",
|
||||||
"vue-clipboard3": "^2.0.0",
|
"vue-clipboard3": "^2.0.0",
|
||||||
"vue-codemirror": "^6.1.1",
|
"vue-codemirror": "^6.1.1",
|
||||||
"vue-i18n": "^9.13.1",
|
"vue-i18n": "^9.13.1",
|
||||||
"vue-router": "^4.2.4",
|
"vue-router": "^4.2.4"
|
||||||
"recorder-core": "^1.3.24040900"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@rushstack/eslint-patch": "^1.3.2",
|
"@rushstack/eslint-patch": "^1.3.2",
|
||||||
|
|
|
||||||
|
|
@ -95,6 +95,9 @@ const getFunctionLibById: (
|
||||||
) => Promise<Result<any>> = (function_lib_id, loading) => {
|
) => Promise<Result<any>> = (function_lib_id, loading) => {
|
||||||
return get(`${prefix}/${function_lib_id}`, undefined, 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 {
|
export default {
|
||||||
getFunctionLib,
|
getFunctionLib,
|
||||||
|
|
@ -103,5 +106,6 @@ export default {
|
||||||
postFunctionLibDebug,
|
postFunctionLibDebug,
|
||||||
getAllFunctionLib,
|
getAllFunctionLib,
|
||||||
delFunctionLib,
|
delFunctionLib,
|
||||||
getFunctionLibById
|
getFunctionLibById,
|
||||||
|
pylint
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,27 +1,55 @@
|
||||||
<template>
|
<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>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, onMounted, onUnmounted } from 'vue'
|
import { ref } from 'vue'
|
||||||
import { Codemirror } from 'vue-codemirror'
|
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' })
|
defineOptions({ name: 'CodemirrorEditor' })
|
||||||
const cmOption = {
|
|
||||||
tabSize: 4,
|
function getRangeFromLineAndColumn(state: any, line: number, column: number, end_column?: number) {
|
||||||
lineWrapping: true,
|
const l = state.doc.line(line)
|
||||||
lineNumbers: true,
|
return { form: l.from + column, to: end_column ? l.from + end_column : l.to }
|
||||||
line: true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 = {
|
const codemirrorStyle = {
|
||||||
height: '210px!important'
|
height: '210px!important'
|
||||||
}
|
}
|
||||||
const cmRef = ref()
|
const cmRef = ref<InstanceType<typeof Codemirror>>()
|
||||||
onMounted(() => {})
|
|
||||||
|
|
||||||
onUnmounted(() => {
|
|
||||||
cmRef.value?.destroy()
|
|
||||||
})
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss"></style>
|
<style lang="scss"></style>
|
||||||
|
|
|
||||||
|
|
@ -721,24 +721,6 @@ h5 {
|
||||||
background: var(--el-color-primary-light-9) !important;
|
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 {
|
.edit-avatar {
|
||||||
position: relative;
|
position: relative;
|
||||||
.edit-mask {
|
.edit-mask {
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@
|
||||||
--app-view-padding: 24px;
|
--app-view-padding: 24px;
|
||||||
--app-view-bg-color: #ffffff;
|
--app-view-bg-color: #ffffff;
|
||||||
--app-border-color-dark: #bbbfc4;
|
--app-border-color-dark: #bbbfc4;
|
||||||
|
--md-bk-hover-color:var(--el-border-color-hover);
|
||||||
/** header 组件 */
|
/** header 组件 */
|
||||||
--app-header-height: 56px;
|
--app-header-height: 56px;
|
||||||
--app-header-padding: 0 20px;
|
--app-header-padding: 0 20px;
|
||||||
|
|
|
||||||
|
|
@ -305,4 +305,13 @@ defineExpose({
|
||||||
open
|
open
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
<style lang="scss" scoped></style>
|
<style lang="scss" scoped>
|
||||||
|
.function-CodemirrorEditor__footer {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 10px;
|
||||||
|
right: 10px;
|
||||||
|
}
|
||||||
|
.function-CodemirrorEditor {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -221,4 +221,13 @@ onMounted(() => {
|
||||||
}, 100)
|
}, 100)
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
<style lang="scss"></style>
|
<style lang="scss">
|
||||||
|
.function-CodemirrorEditor__footer {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 10px;
|
||||||
|
right: 10px;
|
||||||
|
}
|
||||||
|
.function-CodemirrorEditor {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue