-
Notifications
You must be signed in to change notification settings - Fork 2.8k
feat: add import and export endpoints to ToolView for workspace tools #2951
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,93 @@ | ||
| # coding=utf-8 | ||
|
|
||
| import os | ||
| import subprocess | ||
| import sys | ||
| import uuid | ||
| from textwrap import dedent | ||
|
|
||
| from diskcache import Cache | ||
|
|
||
| from maxkb.const import BASE_DIR | ||
| from maxkb.const import PROJECT_DIR | ||
|
|
||
| python_directory = sys.executable | ||
|
|
||
|
|
||
| class ToolExecutor: | ||
| def __init__(self, sandbox=False): | ||
| self.sandbox = sandbox | ||
| if sandbox: | ||
| self.sandbox_path = '/opt/maxkb/app/sandbox' | ||
| self.user = 'sandbox' | ||
| else: | ||
| self.sandbox_path = os.path.join(PROJECT_DIR, 'data', 'sandbox') | ||
| self.user = None | ||
| self._createdir() | ||
| if self.sandbox: | ||
| os.system(f"chown -R {self.user}:root {self.sandbox_path}") | ||
|
|
||
| def _createdir(self): | ||
| old_mask = os.umask(0o077) | ||
| try: | ||
| os.makedirs(self.sandbox_path, 0o700, exist_ok=True) | ||
| finally: | ||
| os.umask(old_mask) | ||
|
|
||
| def exec_code(self, code_str, keywords): | ||
| _id = str(uuid.uuid1()) | ||
| success = '{"code":200,"msg":"成功","data":exec_result}' | ||
| err = '{"code":500,"msg":str(e),"data":None}' | ||
| path = r'' + self.sandbox_path + '' | ||
| _exec_code = f""" | ||
| try: | ||
| import os | ||
| env = dict(os.environ) | ||
| for key in list(env.keys()): | ||
| if key in os.environ and (key.startswith('MAXKB') or key.startswith('POSTGRES') or key.startswith('PG')): | ||
| del os.environ[key] | ||
| locals_v={'{}'} | ||
| keywords={keywords} | ||
| globals_v=globals() | ||
| exec({dedent(code_str)!a}, globals_v, locals_v) | ||
| f_name, f = locals_v.popitem() | ||
| for local in locals_v: | ||
| globals_v[local] = locals_v[local] | ||
| exec_result=f(**keywords) | ||
| from diskcache import Cache | ||
| cache = Cache({path!a}) | ||
| cache.set({_id!a},{success}) | ||
| except Exception as e: | ||
| from diskcache import Cache | ||
| cache = Cache({path!a}) | ||
| cache.set({_id!a},{err}) | ||
| """ | ||
| if self.sandbox: | ||
| subprocess_result = self._exec_sandbox(_exec_code, _id) | ||
| else: | ||
| subprocess_result = self._exec(_exec_code) | ||
| if subprocess_result.returncode == 1: | ||
| raise Exception(subprocess_result.stderr) | ||
| cache = Cache(self.sandbox_path) | ||
| result = cache.get(_id) | ||
| cache.delete(_id) | ||
| if result.get('code') == 200: | ||
| return result.get('data') | ||
| raise Exception(result.get('msg')) | ||
|
|
||
| def _exec_sandbox(self, _code, _id): | ||
| exec_python_file = f'{self.sandbox_path}/{_id}.py' | ||
| with open(exec_python_file, 'w') as file: | ||
| file.write(_code) | ||
| os.system(f"chown {self.user}:{self.user} {exec_python_file}") | ||
| kwargs = {'cwd': BASE_DIR} | ||
| subprocess_result = subprocess.run( | ||
| ['su', '-s', python_directory, '-c', "exec(open('" + exec_python_file + "').read())", self.user], | ||
| text=True, | ||
| capture_output=True, **kwargs) | ||
| os.remove(exec_python_file) | ||
| return subprocess_result | ||
|
|
||
| @staticmethod | ||
| def _exec(_code): | ||
| return subprocess.run([python_directory, '-c', _code], text=True, capture_output=True) | ||
|
liuruibin marked this conversation as resolved.
liuruibin marked this conversation as resolved.
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -4,7 +4,7 @@ | |
|
|
||
| from common.mixins.api_mixin import APIMixin | ||
| from common.result import ResultSerializer, DefaultResultSerializer | ||
| from tools.serializers.tool import ToolModelSerializer, ToolCreateRequest | ||
| from tools.serializers.tool import ToolModelSerializer, ToolCreateRequest, ToolDebugRequest | ||
|
|
||
|
|
||
| class ToolCreateResponse(ResultSerializer): | ||
|
|
@@ -91,3 +91,104 @@ def get_parameters(): | |
| required=False, | ||
| ) | ||
| ] | ||
|
|
||
|
|
||
| class ToolDebugApi(APIMixin): | ||
| @staticmethod | ||
| def get_request(): | ||
| return ToolDebugRequest | ||
|
|
||
| @staticmethod | ||
| def get_response(): | ||
| return DefaultResultSerializer | ||
|
|
||
|
|
||
| class ToolExportAPI(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_response(): | ||
| return DefaultResultSerializer | ||
|
|
||
|
|
||
| class ToolImportAPI(APIMixin): | ||
| @staticmethod | ||
| def get_parameters(): | ||
| return [ | ||
| OpenApiParameter( | ||
| name="workspace_id", | ||
| description="工作空间id", | ||
| type=OpenApiTypes.STR, | ||
| location='path', | ||
| required=True, | ||
| ), | ||
| OpenApiParameter( | ||
| name='file', | ||
| type=OpenApiTypes.BINARY, | ||
| description='工具文件', | ||
| required=True | ||
| ), | ||
| ] | ||
|
|
||
| @staticmethod | ||
| def get_response(): | ||
| return DefaultResultSerializer | ||
|
|
||
|
|
||
| class ToolPageAPI(ToolReadAPI): | ||
| @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, | ||
| ), | ||
| OpenApiParameter( | ||
| name="current_page", | ||
| description="当前页码", | ||
| type=OpenApiTypes.INT, | ||
| location='path', | ||
| required=True, | ||
| ), | ||
| OpenApiParameter( | ||
| name="page_size", | ||
| description="每页大小", | ||
| type=OpenApiTypes.INT, | ||
| location='path', | ||
| required=True, | ||
| ), | ||
| OpenApiParameter( | ||
| name="name", | ||
| description="工具名称", | ||
| type=OpenApiTypes.STR, | ||
| location='query', | ||
| required=False, | ||
| ), | ||
| ] | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The provided Code looks mostly clean, but there are a few minor improvements and concerns:
Here's a revised version with some suggested improvements: # Import necessary packages
from common.mixins.api_mixin import APIMixin
from common.result import DefaultResultSerializer
# Update the serializer imports
from tools.serializers.tool import (
ToolModelSerializer,
ToolCreateRequest,
ToolDebugRequest
)
class UserAuth(APIView):
# Static Method: Get debug request object
@staticmethod
def get_debug_api():
return ToolDebugRequest
# Static Method: Get debug response object
@staticmethod
def get_debug_response():
return DefaultResultSerializer
class ExportAPiMixin(APIView):
# Function to define export API parameters
@staticmethod
def get_export_params(workspace_id_required=True):
params = []
workspace_param = ModelField(
name="workspace_id",
type=StringType(required=workspace_id_required)
)
tool_param = ModelField(
name="tool_id",
type=StringType(required=not workspace_id_required)
)
current_page_param = QueryField(
name="current_page",
type=IntegerType(min_value=1),
required=False
)
page_size_param = QueryField(
name="page_size",
type=IntegerType(min_value=1),
required=False
)
name_param = QueryField(
name="name",
type=StringType(),
required=False
)
query_fields.append(
FilterParams(
"WorkspaceID|toolID&Query",
[workspace_param, tool_param],
[current_page_param], # optional fields
[pagesize_param]
)
)
return query
# Usage example:
export_api = ExportAPIMixin()
print(export_api.get_export_params(workspace_id_required=True))Key Changes & Suggestions:
This should help ensure clearer documentation and possibly smoother integration into production environments. |
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
/opt/maxkb/app/sandbox -> /opt/maxkb-app/sandbox