deepdata: core product code (backend, frontend, runtime, deepflow module)
This commit is contained in:
@@ -0,0 +1,11 @@
|
||||
"""
|
||||
Operator Market Services
|
||||
算子市场服务层
|
||||
"""
|
||||
from .operator_service import OperatorService
|
||||
from .category_service import CategoryService
|
||||
|
||||
__all__ = [
|
||||
"OperatorService",
|
||||
"CategoryService",
|
||||
]
|
||||
@@ -0,0 +1,110 @@
|
||||
"""
|
||||
Category Service
|
||||
分类服务层
|
||||
"""
|
||||
|
||||
from typing import List, Optional
|
||||
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.module.operator.repository import (
|
||||
CategoryRepository,
|
||||
CategoryRelationRepository,
|
||||
)
|
||||
from app.module.operator.schema import (
|
||||
CategoryDto,
|
||||
CategoryTreeResponse,
|
||||
CategoryTreePagedResponse,
|
||||
)
|
||||
from app.db.models.operator import Operator
|
||||
from app.module.operator.repository.operator_repository import OperatorRepository
|
||||
|
||||
|
||||
class CategoryService:
|
||||
"""分类服务"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
category_repo: CategoryRepository,
|
||||
category_relation_repo: CategoryRelationRepository,
|
||||
operator_repo: OperatorRepository,
|
||||
):
|
||||
self.category_repo = category_repo
|
||||
self.category_relation_repo = category_relation_repo
|
||||
self.operator_repo = operator_repo
|
||||
|
||||
async def get_all_categories(
|
||||
self, db: AsyncSession, locale: Optional[str] = None
|
||||
) -> CategoryTreePagedResponse:
|
||||
"""获取所有分类(树状结构)
|
||||
|
||||
Args:
|
||||
db: 数据库会话
|
||||
locale: 语言代码 (zh/en),None 时返回中文
|
||||
"""
|
||||
|
||||
# Helper function to get localized name
|
||||
def get_localized_name(cat) -> str:
|
||||
if locale == "en" and getattr(cat, "name_en", None):
|
||||
return cat.name_en
|
||||
return cat.name
|
||||
|
||||
# Get all categories
|
||||
all_categories = await self.category_repo.find_all(db)
|
||||
category_map = {c.id: c for c in all_categories}
|
||||
|
||||
# Get all relations and count operators per category
|
||||
all_relations = await self.category_relation_repo.find_all(db)
|
||||
relation_map = {}
|
||||
for rel in all_relations:
|
||||
if rel.category_id not in relation_map:
|
||||
relation_map[rel.category_id] = 0
|
||||
relation_map[rel.category_id] += 1
|
||||
|
||||
# Group by parent_id
|
||||
grouped_by_parent = {}
|
||||
for cat in all_categories:
|
||||
if cat.parent_id != "0":
|
||||
if cat.parent_id not in grouped_by_parent:
|
||||
grouped_by_parent[cat.parent_id] = []
|
||||
grouped_by_parent[cat.parent_id].append(cat)
|
||||
|
||||
# Build category trees
|
||||
parent_ids = sorted(grouped_by_parent.keys(), key=lambda pid: pid)
|
||||
|
||||
category_trees = []
|
||||
for parent_id in parent_ids:
|
||||
group = grouped_by_parent[parent_id]
|
||||
parent_category = category_map[parent_id]
|
||||
|
||||
# Build DTOs for children
|
||||
child_dtos = []
|
||||
total_count = 0
|
||||
for cat in sorted(group, key=lambda c: c.created_at or 0):
|
||||
cat_dto = CategoryDto(
|
||||
id=cat.id,
|
||||
name=get_localized_name(cat),
|
||||
value=cat.value,
|
||||
type=cat.type,
|
||||
parent_id=cat.parent_id,
|
||||
count=relation_map.get(cat.id, 0),
|
||||
created_at=cat.created_at,
|
||||
)
|
||||
child_dtos.append(cat_dto)
|
||||
total_count += cat_dto.count
|
||||
|
||||
tree = CategoryTreeResponse(
|
||||
id=parent_id,
|
||||
name=get_localized_name(parent_category),
|
||||
count=total_count,
|
||||
categories=child_dtos,
|
||||
)
|
||||
category_trees.append(tree)
|
||||
|
||||
# Get star count
|
||||
star_count = await self.operator_repo.count_by_star(True, db)
|
||||
|
||||
return CategoryTreePagedResponse(
|
||||
star_count=star_count,
|
||||
categories=category_trees,
|
||||
)
|
||||
@@ -0,0 +1,637 @@
|
||||
"""
|
||||
Operator Service
|
||||
算子服务层
|
||||
"""
|
||||
import json
|
||||
import os
|
||||
import uuid
|
||||
import shutil
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
from typing import List, Optional, Dict, Any, TYPE_CHECKING
|
||||
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy import select, text, func
|
||||
|
||||
from app.core.logging import get_logger
|
||||
from app.core.exception import BusinessError, ErrorCodes
|
||||
from app.module.operator.repository import (
|
||||
OperatorRepository,
|
||||
CategoryRelationRepository,
|
||||
OperatorReleaseRepository,
|
||||
)
|
||||
from app.module.operator.schema import (
|
||||
OperatorDto,
|
||||
OperatorUpdateDto,
|
||||
OperatorReleaseDto,
|
||||
)
|
||||
from app.module.operator.parsers import ParserHolder
|
||||
from app.module.operator.constants import (
|
||||
OPERATOR_BASE_PATH,
|
||||
UPLOAD_DIR,
|
||||
EXTRACT_DIR,
|
||||
YAML_PATH,
|
||||
SERVICE_ID,
|
||||
)
|
||||
from app.module.shared.file_service import FileService
|
||||
from app.module.shared.file_models import (
|
||||
ChunkUploadRequestDto,
|
||||
FileUploadResult,
|
||||
)
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
|
||||
class OperatorService:
|
||||
"""算子服务"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
operator_repo: OperatorRepository,
|
||||
category_relation_repo: CategoryRelationRepository,
|
||||
operator_release_repo: OperatorReleaseRepository,
|
||||
parser_holder: ParserHolder,
|
||||
file_service: FileService,
|
||||
):
|
||||
self.operator_repo = operator_repo
|
||||
self.category_relation_repo = category_relation_repo
|
||||
self.operator_release_repo = operator_release_repo
|
||||
self.parser_holder = parser_holder
|
||||
self.file_service = file_service
|
||||
|
||||
async def get_operators(
|
||||
self,
|
||||
page: int,
|
||||
size: int,
|
||||
categories: List[List[str]],
|
||||
keyword: Optional[str],
|
||||
is_star: Optional[bool],
|
||||
db: AsyncSession
|
||||
) -> List[OperatorDto]:
|
||||
"""查询算子列表(分页)"""
|
||||
offset = page * size
|
||||
|
||||
# Build query with categories filter
|
||||
conditions = []
|
||||
params = {"limit": size, "offset": offset}
|
||||
|
||||
if is_star is not None:
|
||||
conditions.append("ov.is_star = :is_star")
|
||||
params["is_star"] = is_star
|
||||
|
||||
if keyword:
|
||||
conditions.append(
|
||||
"(ov.operator_name ILIKE :keyword OR ov.description ILIKE :keyword)"
|
||||
)
|
||||
params["keyword"] = f"%{keyword}%"
|
||||
|
||||
where_clause = ""
|
||||
if conditions:
|
||||
where_clause = "WHERE " + " AND ".join(conditions)
|
||||
|
||||
# Handle categories grouping
|
||||
group_by = "GROUP BY ov.operator_id, ov.operator_name, ov.description, ov.version, " \
|
||||
"ov.inputs, ov.outputs, ov.runtime, ov.settings, ov.is_star, " \
|
||||
"ov.file_size, ov.usage_count, ov.created_at, ov.updated_at, ov.created_by, ov.updated_by"
|
||||
|
||||
having_clause = ""
|
||||
if categories:
|
||||
# Flatten all category IDs for IN clause
|
||||
all_category_ids = [cat_id for sublist in categories for cat_id in sublist]
|
||||
if all_category_ids:
|
||||
where_clause += " AND category_id = ANY(:category_ids)" if where_clause else "WHERE category_id = ANY(:category_ids)"
|
||||
params["category_ids"] = all_category_ids
|
||||
|
||||
# Build HAVING clause for category groups
|
||||
having_clauses = []
|
||||
for i, cat_group in enumerate(categories):
|
||||
cat_list = ", ".join([f"'{cat_id}'" for cat_id in cat_group])
|
||||
having_clauses.append(
|
||||
f"SUM(CASE WHEN category_id IN ({cat_list}) THEN 1 ELSE 0 END) > 0"
|
||||
)
|
||||
having_clause = "HAVING " + " AND ".join(having_clauses)
|
||||
|
||||
query = f"""
|
||||
SELECT
|
||||
ov.operator_id AS id,
|
||||
ov.operator_name AS name,
|
||||
ov.description,
|
||||
ov.version,
|
||||
ov.inputs,
|
||||
ov.outputs,
|
||||
ov.runtime,
|
||||
ov.settings,
|
||||
ov.is_star,
|
||||
ov.file_size,
|
||||
ov.usage_count,
|
||||
ov.created_at,
|
||||
ov.updated_at,
|
||||
string_agg(ov.category_id, ',' ORDER BY ov.created_at DESC) AS categories
|
||||
FROM v_operator ov
|
||||
{where_clause}
|
||||
{group_by}
|
||||
{having_clause}
|
||||
ORDER BY ov.created_at DESC
|
||||
LIMIT :limit OFFSET :offset
|
||||
"""
|
||||
|
||||
result = await db.execute(text(query), params)
|
||||
rows = result.fetchall()
|
||||
|
||||
# Convert to DTOs
|
||||
operators = []
|
||||
for row in rows:
|
||||
categories_list = []
|
||||
if row.categories:
|
||||
categories_list = [cat_id for cat_id in row.categories.split(',') if cat_id]
|
||||
|
||||
operators.append(OperatorDto(
|
||||
id=row.id,
|
||||
name=row.name,
|
||||
description=row.description,
|
||||
version=row.version,
|
||||
inputs=row.inputs,
|
||||
outputs=row.outputs,
|
||||
runtime=row.runtime,
|
||||
settings=row.settings,
|
||||
file_name=None,
|
||||
file_size=row.file_size,
|
||||
metrics=None,
|
||||
usage_count=row.usage_count,
|
||||
is_star=row.is_star,
|
||||
categories=categories_list,
|
||||
created_at=row.created_at,
|
||||
updated_at=row.updated_at,
|
||||
))
|
||||
|
||||
return operators
|
||||
|
||||
async def count_operators(
|
||||
self,
|
||||
categories: List[List[str]],
|
||||
keyword: Optional[str],
|
||||
is_star: Optional[bool],
|
||||
db: AsyncSession
|
||||
) -> int:
|
||||
"""统计算子数量"""
|
||||
conditions = []
|
||||
params = {}
|
||||
|
||||
if is_star is not None:
|
||||
conditions.append("is_star = :is_star")
|
||||
params["is_star"] = is_star
|
||||
|
||||
if keyword:
|
||||
conditions.append(
|
||||
"(operator_name ILIKE :keyword OR description ILIKE :keyword)"
|
||||
)
|
||||
params["keyword"] = f"%{keyword}%"
|
||||
|
||||
where_clause = ""
|
||||
if conditions:
|
||||
where_clause = "WHERE " + " AND ".join(conditions)
|
||||
|
||||
# Handle categories grouping
|
||||
group_by = "GROUP BY operator_id, operator_name, description, version, inputs, outputs, " \
|
||||
"runtime, settings, is_star, file_size, usage_count, created_at, updated_at, " \
|
||||
"created_by, updated_by"
|
||||
|
||||
having_clause = ""
|
||||
if categories:
|
||||
# Flatten all category IDs for IN clause
|
||||
all_category_ids = [cat_id for sublist in categories for cat_id in sublist]
|
||||
if all_category_ids:
|
||||
where_clause += " AND category_id = ANY(:category_ids)" if where_clause else "WHERE category_id = ANY(:category_ids)"
|
||||
params["category_ids"] = all_category_ids
|
||||
|
||||
# Build HAVING clause for category groups
|
||||
having_clauses = []
|
||||
for i, cat_group in enumerate(categories):
|
||||
cat_list = ", ".join([f"'{cat_id}'" for cat_id in cat_group])
|
||||
having_clauses.append(
|
||||
f"SUM(CASE WHEN category_id IN ({cat_list}) THEN 1 ELSE 0 END) > 0"
|
||||
)
|
||||
having_clause = "HAVING " + " AND ".join(having_clauses)
|
||||
|
||||
query = f"""
|
||||
SELECT COUNT(*) as count
|
||||
FROM (
|
||||
SELECT operator_id
|
||||
FROM v_operator
|
||||
{where_clause}
|
||||
{group_by}
|
||||
{having_clause}
|
||||
) AS t
|
||||
"""
|
||||
|
||||
result = await db.execute(text(query), params)
|
||||
return result.scalar() or 0
|
||||
|
||||
async def get_operator_by_id(
|
||||
self,
|
||||
operator_id: str,
|
||||
db: AsyncSession,
|
||||
locale: Optional[str] = None
|
||||
) -> OperatorDto:
|
||||
"""根据 ID 获取算子详情"""
|
||||
attr = "category_name"
|
||||
if locale == "en":
|
||||
attr = "category_name_en"
|
||||
|
||||
result = await db.execute(
|
||||
text(f"""
|
||||
SELECT
|
||||
operator_id, operator_name, description, version, inputs, outputs, runtime,
|
||||
settings, is_star, file_name, file_size, usage_count, metrics,
|
||||
created_at, updated_at, created_by, updated_by,
|
||||
string_agg({attr}, ',' ORDER BY created_at DESC) AS categories
|
||||
FROM v_operator
|
||||
WHERE operator_id = :operator_id
|
||||
GROUP BY operator_id, operator_name, description, version, inputs, outputs, runtime,
|
||||
settings, is_star, file_name, file_size, usage_count, metrics,
|
||||
created_at, updated_at, created_by, updated_by
|
||||
"""),
|
||||
{"operator_id": operator_id}
|
||||
)
|
||||
row = result.fetchone()
|
||||
|
||||
if not row:
|
||||
raise BusinessError(ErrorCodes.OPERATOR_NOT_FOUND, operator_id)
|
||||
|
||||
# Parse categories from comma-separated string
|
||||
categories_str = row.categories if hasattr(row, 'categories') and row.categories else ""
|
||||
categories = [c.strip() for c in categories_str.split(",")] if categories_str else []
|
||||
|
||||
# Build DTO
|
||||
operator = OperatorDto(
|
||||
id=row.operator_id,
|
||||
name=row.operator_name,
|
||||
description=row.description,
|
||||
version=row.version,
|
||||
inputs=row.inputs,
|
||||
outputs=row.outputs,
|
||||
runtime=row.runtime,
|
||||
settings=row.settings,
|
||||
file_name=row.file_name,
|
||||
file_size=row.file_size,
|
||||
metrics=row.metrics,
|
||||
usage_count=row.usage_count,
|
||||
is_star=row.is_star,
|
||||
created_at=row.created_at,
|
||||
updated_at=row.updated_at,
|
||||
categories=categories,
|
||||
)
|
||||
|
||||
# Read requirements and readme if file exists
|
||||
if row.file_name:
|
||||
extract_path = self._get_extract_path(
|
||||
self._get_stem(row.file_name)
|
||||
)
|
||||
operator.requirements = self._read_requirements(extract_path)
|
||||
operator.readme = self._get_readme_content(extract_path)
|
||||
|
||||
# Load releases
|
||||
releases = await self.operator_release_repo.find_all_by_operator_id(
|
||||
operator_id, db
|
||||
)
|
||||
operator.releases = [
|
||||
OperatorReleaseDto(
|
||||
id=release.id,
|
||||
version=release.version,
|
||||
release_date=release.release_date,
|
||||
changelog=release.changelog
|
||||
)
|
||||
for release in releases
|
||||
]
|
||||
|
||||
return operator
|
||||
|
||||
async def create_operator(
|
||||
self,
|
||||
req: OperatorDto,
|
||||
db: AsyncSession
|
||||
) -> OperatorDto:
|
||||
"""创建算子"""
|
||||
|
||||
# Generate ID if not provided
|
||||
if not req.id:
|
||||
req.id = str(uuid.uuid4())
|
||||
|
||||
# Override settings
|
||||
self._override_settings(req)
|
||||
|
||||
# Insert operator
|
||||
await self.operator_repo.insert(req, db)
|
||||
await db.flush()
|
||||
|
||||
# Insert category relations
|
||||
if req.categories:
|
||||
await self.category_relation_repo.batch_insert(
|
||||
req.id, req.categories, db
|
||||
)
|
||||
|
||||
# Insert release
|
||||
if req.releases:
|
||||
release = req.releases[0]
|
||||
release.id = req.id
|
||||
release.version = req.version
|
||||
release.release_date = datetime.now()
|
||||
await self.operator_release_repo.insert(release, db)
|
||||
|
||||
# Extract files
|
||||
if req.file_name:
|
||||
self.parser_holder.extract_to(
|
||||
self._get_file_type(req.file_name),
|
||||
self._get_upload_path(req.file_name),
|
||||
self._get_extract_path(self._get_stem(req.file_name))
|
||||
)
|
||||
|
||||
return req
|
||||
|
||||
async def update_operator(
|
||||
self,
|
||||
operator_id: str,
|
||||
req: OperatorUpdateDto,
|
||||
db: AsyncSession
|
||||
) -> OperatorDto:
|
||||
"""更新算子"""
|
||||
|
||||
# Get existing operator
|
||||
existing = await self.get_operator_by_id(operator_id, db)
|
||||
|
||||
# Save original version for release comparison
|
||||
original_version = existing.version
|
||||
|
||||
# Merge update request into existing operator
|
||||
# Only update fields that are provided (not None)
|
||||
if req.name is not None:
|
||||
existing.name = req.name
|
||||
if req.description is not None:
|
||||
existing.description = req.description
|
||||
if req.version is not None:
|
||||
existing.version = req.version
|
||||
if req.inputs is not None:
|
||||
existing.inputs = req.inputs
|
||||
if req.outputs is not None:
|
||||
existing.outputs = req.outputs
|
||||
if req.runtime is not None:
|
||||
existing.runtime = req.runtime
|
||||
if req.settings is not None:
|
||||
existing.settings = req.settings
|
||||
if req.file_name is not None:
|
||||
existing.file_name = req.file_name
|
||||
if req.file_size is not None:
|
||||
existing.file_size = req.file_size
|
||||
if req.metrics is not None:
|
||||
existing.metrics = req.metrics
|
||||
if req.usage_count is not None:
|
||||
existing.usage_count = req.usage_count
|
||||
if req.is_star is not None:
|
||||
existing.is_star = req.is_star
|
||||
if req.categories is not None:
|
||||
existing.categories = req.categories
|
||||
if req.overrides is not None:
|
||||
existing.overrides = req.overrides
|
||||
|
||||
# Override settings
|
||||
self._override_settings(existing)
|
||||
|
||||
# Update operator
|
||||
await self.operator_repo.update(existing, db)
|
||||
|
||||
# Update category relations
|
||||
if req.file_name is not None and req.categories is not None:
|
||||
await self.category_relation_repo.batch_update(
|
||||
operator_id, req.categories, db
|
||||
)
|
||||
|
||||
# Update release
|
||||
if req.releases is not None and len(req.releases) > 0:
|
||||
release = req.releases[0]
|
||||
release.id = operator_id
|
||||
release.version = req.version
|
||||
release.release_date = datetime.now()
|
||||
if original_version == release.version:
|
||||
await self.operator_release_repo.update(release, db)
|
||||
else:
|
||||
await self.operator_release_repo.insert(release, db)
|
||||
|
||||
# Extract files
|
||||
if req.file_name is not None:
|
||||
self.parser_holder.extract_to(
|
||||
self._get_file_type(req.file_name),
|
||||
self._get_upload_path(req.file_name),
|
||||
self._get_extract_path(self._get_stem(req.file_name))
|
||||
)
|
||||
|
||||
await db.flush()
|
||||
return await self.get_operator_by_id(operator_id, db)
|
||||
|
||||
async def delete_operator(
|
||||
self,
|
||||
operator_id: str,
|
||||
db: AsyncSession
|
||||
) -> None:
|
||||
"""删除算子"""
|
||||
# Check if operator is in use
|
||||
in_template = await self.operator_repo.operator_in_template(operator_id, db)
|
||||
in_unstop_task = await self.operator_repo.operator_in_unstop_task(operator_id, db)
|
||||
if in_template or in_unstop_task:
|
||||
raise BusinessError(ErrorCodes.OPERATOR_IN_INSTANCE)
|
||||
|
||||
# Check if operator is predefined
|
||||
is_predefined = await self.category_relation_repo.operator_is_predefined(
|
||||
operator_id, db
|
||||
)
|
||||
if is_predefined:
|
||||
raise BusinessError(ErrorCodes.OPERATOR_CANNOT_DELETE_PREDEFINED)
|
||||
|
||||
# Get operator for file cleanup
|
||||
operator = await self.get_operator_by_id(operator_id, db)
|
||||
|
||||
# Delete from database
|
||||
await self.operator_repo.delete(operator_id, db)
|
||||
await self.category_relation_repo.delete_by_operator_id(operator_id, db)
|
||||
await self.operator_release_repo.delete(operator_id, db)
|
||||
|
||||
# Delete extracted files
|
||||
if operator.file_name:
|
||||
extract_path = self._get_extract_path(self._get_stem(operator.file_name))
|
||||
shutil.rmtree(extract_path, ignore_errors=True)
|
||||
|
||||
async def upload_operator(
|
||||
self,
|
||||
file_name: str,
|
||||
db: AsyncSession
|
||||
) -> OperatorDto:
|
||||
"""上传算子文件并解析元数据"""
|
||||
file_path = self._get_upload_path(file_name)
|
||||
file_size = os.path.getsize(file_path) if os.path.exists(file_path) else None
|
||||
return self.parser_holder.parse_yaml_from_archive(
|
||||
self._get_file_type(file_name),
|
||||
file_path,
|
||||
YAML_PATH,
|
||||
file_name,
|
||||
file_size
|
||||
)
|
||||
|
||||
async def pre_upload(self, db: AsyncSession) -> str:
|
||||
"""预上传,返回请求 ID"""
|
||||
from app.module.operator.constants import OPERATOR_BASE_PATH, UPLOAD_DIR
|
||||
|
||||
upload_path = os.path.join(OPERATOR_BASE_PATH, UPLOAD_DIR)
|
||||
req_id = await self.file_service.pre_upload(
|
||||
upload_path=upload_path,
|
||||
service_id=SERVICE_ID,
|
||||
db_session=db,
|
||||
check_info=None
|
||||
)
|
||||
return req_id
|
||||
|
||||
async def chunk_upload(
|
||||
self,
|
||||
req_id: str,
|
||||
file_no: int,
|
||||
file_name: str,
|
||||
total_chunk_num: int,
|
||||
chunk_no: int,
|
||||
check_sum_hex: Optional[str],
|
||||
file_content: bytes,
|
||||
db: AsyncSession
|
||||
) -> FileUploadResult:
|
||||
"""分块上传文件"""
|
||||
from app.module.operator.constants import OPERATOR_BASE_PATH, UPLOAD_DIR
|
||||
|
||||
upload_path = os.path.join(OPERATOR_BASE_PATH, UPLOAD_DIR)
|
||||
|
||||
chunk_request = ChunkUploadRequestDto(
|
||||
req_id=req_id,
|
||||
file_no=file_no,
|
||||
file_name=file_name,
|
||||
total_chunk_num=total_chunk_num,
|
||||
chunk_no=chunk_no,
|
||||
check_sum_hex=check_sum_hex,
|
||||
)
|
||||
|
||||
return await self.file_service.chunk_upload(
|
||||
chunk_request, upload_path, file_content, db
|
||||
)
|
||||
|
||||
def download_example_operator(self, file_path: str) -> Path:
|
||||
"""下载示例算子文件"""
|
||||
path = Path(file_path)
|
||||
if not path.exists():
|
||||
raise FileNotFoundError(f"File not found: {file_path}")
|
||||
return path
|
||||
|
||||
def _override_settings(self, operator: OperatorDto) -> None:
|
||||
"""用 overrides 值覆盖 settings 的 defaultVal"""
|
||||
if not operator.settings or not operator.overrides:
|
||||
return
|
||||
|
||||
try:
|
||||
settings = json.loads(operator.settings)
|
||||
for key, value in operator.overrides.items():
|
||||
if key not in settings:
|
||||
continue
|
||||
|
||||
setting = settings[key]
|
||||
setting_type = setting.get("type")
|
||||
|
||||
match setting_type:
|
||||
case "slider" | "switch" | "select" | "input" | "radio":
|
||||
setting["defaultVal"] = value
|
||||
case "checkbox":
|
||||
setting["defaultVal"] = self._convert_to_list_string(value)
|
||||
case "range":
|
||||
self._update_properties(setting, value)
|
||||
|
||||
settings[key] = setting
|
||||
|
||||
operator.settings = json.dumps(settings)
|
||||
except json.JSONDecodeError as e:
|
||||
raise BusinessError(ErrorCodes.OPERATOR_PARSE_FAILED, str(e))
|
||||
|
||||
def _convert_to_list_string(self, value: Any) -> str:
|
||||
"""转换为逗号分隔的字符串"""
|
||||
if value is None:
|
||||
return ""
|
||||
if isinstance(value, list):
|
||||
return ",".join(str(v) for v in value)
|
||||
return str(value)
|
||||
|
||||
def _update_properties(self, setting: Dict[str, Any], value: Any) -> None:
|
||||
"""更新 range 类型的 properties"""
|
||||
if not isinstance(value, list):
|
||||
return
|
||||
|
||||
properties = setting.get("properties", [])
|
||||
if not isinstance(properties, list) or len(properties) != len(value):
|
||||
return
|
||||
|
||||
for i, prop in enumerate(properties):
|
||||
if isinstance(prop, dict):
|
||||
prop["defaultVal"] = value[i]
|
||||
|
||||
setting["properties"] = properties
|
||||
|
||||
def _read_requirements(self, extract_path: str) -> List[str]:
|
||||
"""读取 requirements.txt"""
|
||||
requirements_path = Path(extract_path) / "requirements.txt"
|
||||
if not requirements_path.exists():
|
||||
return []
|
||||
|
||||
requirements = []
|
||||
try:
|
||||
with open(requirements_path, 'r', encoding='utf-8') as f:
|
||||
for line in f:
|
||||
line = line.strip()
|
||||
if line and not line.startswith('#'):
|
||||
requirements.append(line)
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to read requirements: {e}")
|
||||
return requirements
|
||||
|
||||
def _get_readme_content(self, extract_path: str) -> str:
|
||||
"""读取 README 内容"""
|
||||
dir_path = Path(extract_path)
|
||||
if not dir_path.exists() or not dir_path.is_dir():
|
||||
logger.info(f"Directory does not exist or is not a directory: {extract_path}")
|
||||
return ""
|
||||
|
||||
candidates = ["README.md", "readme.md", "Readme.md"]
|
||||
for filename in candidates:
|
||||
readme_path = dir_path / filename
|
||||
if readme_path.exists() and readme_path.is_file():
|
||||
try:
|
||||
content = readme_path.read_text(encoding='utf-8')
|
||||
logger.info(f"Successfully read README from: {readme_path}")
|
||||
return content
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to read README from {readme_path}: {e}")
|
||||
logger.info(f"No README found in: {extract_path}")
|
||||
return ""
|
||||
|
||||
def _get_file_type(self, file_name: str) -> str:
|
||||
"""获取文件类型(扩展名)"""
|
||||
return file_name.rsplit('.', 1)[-1].lower() if '.' in file_name else ""
|
||||
|
||||
def _get_stem(self, file_name: str) -> str:
|
||||
"""获取文件名不含扩展名"""
|
||||
return file_name.rsplit('.', 1)[0] if '.' in file_name else file_name
|
||||
|
||||
def _get_upload_path(self, file_name: str) -> str:
|
||||
"""获取上传文件路径"""
|
||||
return os.path.join(OPERATOR_BASE_PATH, UPLOAD_DIR, file_name)
|
||||
|
||||
async def increment_usage_count(
|
||||
self,
|
||||
operator_ids: List[str],
|
||||
db: AsyncSession
|
||||
) -> None:
|
||||
"""增加算子使用次数"""
|
||||
await self.operator_repo.increment_usage_count(operator_ids, db)
|
||||
|
||||
def _get_extract_path(self, file_stem: str) -> str:
|
||||
"""获取解压路径"""
|
||||
return os.path.join(OPERATOR_BASE_PATH, EXTRACT_DIR, file_stem)
|
||||
Reference in New Issue
Block a user