98 lines
3.3 KiB
Python
98 lines
3.3 KiB
Python
"""外部系统 provider 注册表。
|
||
|
||
标准 OpenAPI 系统通过数据库配置接入;只有非 OpenAPI 协议才需要新增 connector 文件。
|
||
"""
|
||
from __future__ import annotations
|
||
|
||
from dataclasses import dataclass
|
||
from typing import Any
|
||
|
||
from .auth import ExternalAuthError, get_auth_strategy
|
||
|
||
|
||
FACTORY_QUERY_GUIDANCE = (
|
||
"产量、良率、缺陷、库存、绩效、趋势和按日/月汇总等统计聚合查询,"
|
||
"统一先调用 BI dataset list,再执行匹配的数据集。日志和业务明细列表用于"
|
||
"用户明确要求查看逐条记录、编号或追溯过程的场景。未匹配到 dataset 时,"
|
||
"先限定范围或向用户确认明细查询需求。"
|
||
)
|
||
FACTORY_RECOMMENDED_OPERATIONS = ("bi_dataset_list", "bi_dataset_exec")
|
||
|
||
|
||
@dataclass(frozen=True)
|
||
class ProviderSpec:
|
||
provider: str
|
||
title: str
|
||
connector: str
|
||
default_auth_type: str
|
||
allowed_auth_types: tuple[str, ...]
|
||
defaults: dict[str, Any]
|
||
|
||
|
||
_PROVIDERS = {
|
||
"factory_mes": ProviderSpec(
|
||
provider="factory_mes",
|
||
title="Factory MES",
|
||
connector="openapi",
|
||
default_auth_type="password_jwt",
|
||
allowed_auth_types=("password_jwt",),
|
||
defaults={
|
||
"login_path": "/api/auth/token/",
|
||
"username_field": "username",
|
||
"password_field": "password",
|
||
"token_field": "access",
|
||
"auth_header_name": "Authorization",
|
||
"auth_header_template": "Bearer {token}",
|
||
"query_guidance": FACTORY_QUERY_GUIDANCE,
|
||
"recommended_operation_ids": list(FACTORY_RECOMMENDED_OPERATIONS),
|
||
},
|
||
),
|
||
"generic_openapi": ProviderSpec(
|
||
provider="generic_openapi",
|
||
title="通用 OpenAPI 系统",
|
||
connector="openapi",
|
||
default_auth_type="password_jwt",
|
||
allowed_auth_types=("password_jwt", "api_key", "bearer_token"),
|
||
defaults={
|
||
"login_path": "/api/auth/token/",
|
||
"username_field": "username",
|
||
"password_field": "password",
|
||
"token_field": "access",
|
||
"auth_header_name": "Authorization",
|
||
"auth_header_template": "Bearer {token}",
|
||
"query_guidance": "",
|
||
"recommended_operation_ids": [],
|
||
},
|
||
),
|
||
}
|
||
|
||
|
||
def get_provider(provider: str) -> ProviderSpec:
|
||
result = _PROVIDERS.get((provider or "").strip())
|
||
if result is None:
|
||
raise ValueError(f"不支持的外部系统 provider: {provider}")
|
||
return result
|
||
|
||
|
||
def provider_specs() -> tuple[ProviderSpec, ...]:
|
||
return tuple(_PROVIDERS.values())
|
||
|
||
|
||
def merged_config(provider: str, config: dict[str, Any]) -> dict[str, Any]:
|
||
spec = get_provider(provider)
|
||
result = {**spec.defaults, **(config or {})}
|
||
auth_type = str(result.get("auth_type") or spec.default_auth_type).strip()
|
||
if auth_type not in spec.allowed_auth_types:
|
||
raise ValueError(f"{spec.title} 不支持认证方式 {auth_type}")
|
||
result["auth_type"] = auth_type
|
||
return result
|
||
|
||
|
||
def credential_fields(provider: str, config: dict[str, Any]) -> list[dict[str, Any]]:
|
||
merged = merged_config(provider, config)
|
||
try:
|
||
strategy = get_auth_strategy(merged["auth_type"])
|
||
except ExternalAuthError as exc:
|
||
raise ValueError(str(exc)) from exc
|
||
return [field.as_dict() for field in strategy.credential_fields]
|