feat(ai): gRPC clients 扩展 + server interceptors + proto_gen + 测试 + nextstep 文档

This commit is contained in:
SpecialX
2026-07-14 15:59:41 +08:00
parent fb23c5234e
commit 7b790f1276
21 changed files with 3540 additions and 345 deletions

View File

@@ -5,20 +5,59 @@
客户端:
- ContentClient: 查询知识点/教材/题库content 服务 gRPC 50054
- DataAnaClient: 查询学情/薄弱点/趋势data-ana 服务 gRPC 50055
- IamClient: 查询 DataScopeiam 服务 gRPC 50052P4 补全后启用
- IamClient: 查询 DataScopeiam 服务 gRPC 50052
全并行模式:下游不可用时返回 mock 数据或抛 AI_DOWNSTREAM_UNAVAILABLE 降级。
生产环境使用 *Grpc 类(真实 gRPC 调用);
单元测试使用 *Mock 类(仅返回固定数据,不依赖外部服务)。
"""
from .content_client import ContentClient, ContentClientMock
from .data_ana_client import DataAnaClient, DataAnaClientMock
from .iam_client import IamClient, IamClientMock
from .content_client import (
ContentClient,
ContentClientGrpc,
ContentClientMock,
CreatedQuestion,
KnowledgePoint,
QuestionInput,
)
from .data_ana_client import (
ClassPerformance,
DataAnaClient,
DataAnaClientGrpc,
DataAnaClientMock,
LearningTrend,
StudentScore,
StudentWeakness,
TrendPoint,
WeakPoint,
)
from .iam_client import (
DataScope,
IamClient,
IamClientGrpc,
IamClientMock,
)
__all__ = [
# content
"ContentClient",
"ContentClientGrpc",
"ContentClientMock",
"CreatedQuestion",
"KnowledgePoint",
"QuestionInput",
# data-ana
"ClassPerformance",
"DataAnaClient",
"DataAnaClientGrpc",
"DataAnaClientMock",
"LearningTrend",
"StudentScore",
"StudentWeakness",
"TrendPoint",
"WeakPoint",
# iam
"DataScope",
"IamClient",
"IamClientGrpc",
"IamClientMock",
]

View File

@@ -1,20 +1,22 @@
"""Content 服务 gRPC 客户端.
用于:
- 查询知识点前置依赖GetPrerequisites
- 查询学习路径GetLearningPath
- 创建题目入库(CreateQuestions - P5 mockcontent 服务待补全
- 查询知识点前置依赖(KnowledgeGraphService.GetPrerequisites
- 查询学习路径(KnowledgeGraphService.GetLearningPath
- 创建题目入库(QuestionService.BatchCreateQuestions
全并行模式:下游不可用时使用 mock 数据降级
真实 gRPC 调用:未连接或调用失败时抛 AIError不再降级到 mock 数据
"""
from abc import ABC, abstractmethod
from dataclasses import dataclass
from typing import Any
import grpc
import structlog
from ..errors import AIError, ErrorCode
from ..proto_gen import content_pb2, content_pb2_grpc
from .base_client import BaseGrpcClient
logger = structlog.get_logger()
@@ -29,7 +31,7 @@ class KnowledgePoint:
@dataclass
class QuestionInput:
"""题目入库输入(对应 ConfirmLessonPlan 调用 content.CreateQuestions."""
"""题目入库输入(对应 ConfirmLessonPlan 调用 content.BatchCreateQuestions."""
question: str
answer: str
@@ -83,7 +85,7 @@ class ContentClient(ABC):
class ContentClientMock(ContentClient):
"""Content 客户端 Mock 实现(全并行模式."""
"""Content 客户端 Mock 实现(仅用于单元测试."""
def __init__(self) -> None:
self._available = True
@@ -92,15 +94,8 @@ class ContentClientMock(ContentClient):
self,
knowledge_point_id: str,
) -> list[KnowledgePoint]:
logger.info(
"content_mock_get_prerequisites",
knowledge_point_id=knowledge_point_id,
)
return [
KnowledgePoint(
id="kp_base_001",
title="基础概念mock",
),
KnowledgePoint(id="kp_base_001", title="基础概念mock"),
]
async def get_learning_path(
@@ -108,15 +103,9 @@ class ContentClientMock(ContentClient):
student_id: str,
subject_id: str,
) -> list[KnowledgePoint]:
logger.info(
"content_mock_get_learning_path",
student_id=student_id,
subject_id=subject_id,
)
return [
KnowledgePoint(id="kp_001", title="知识点1mock"),
KnowledgePoint(id="kp_002", title="知识点2mock"),
KnowledgePoint(id="kp_003", title="知识点3mock"),
]
async def create_questions(
@@ -124,16 +113,8 @@ class ContentClientMock(ContentClient):
questions: list[QuestionInput],
user_id: str = "",
) -> list[CreatedQuestion]:
logger.info(
"content_mock_create_questions",
count=len(questions),
user_id=user_id,
)
return [
CreatedQuestion(
id=f"q_mock_{i:04d}",
question=q.question,
)
CreatedQuestion(id=f"q_mock_{i:04d}", question=q.question)
for i, q in enumerate(questions)
]
@@ -141,84 +122,125 @@ class ContentClientMock(ContentClient):
return self._available
class ContentClientGrpc(ContentClient):
"""Content 服务 gRPC 客户端实现.
全并行模式gRPC 调用失败时降级到 mock 数据。
"""
class ContentClientGrpc(BaseGrpcClient, ContentClient):
"""Content 服务 gRPC 客户端实现(真实调用,不降级到 mock."""
def __init__(
self,
endpoint: str = "localhost:50054",
request_id: str = "",
) -> None:
self._endpoint = endpoint
self._request_id = request_id
self._channel: Any = None
self._mock = ContentClientMock()
super().__init__(endpoint, request_id)
self._kg_stub: content_pb2_grpc.KnowledgeGraphServiceStub | None = None
self._q_stub: content_pb2_grpc.QuestionServiceStub | None = None
async def connect(self) -> None:
"""建立 gRPC 连接."""
import grpc
self._channel = grpc.aio.insecure_channel(self._endpoint)
logger.info("content_client_connected", endpoint=self._endpoint)
async def close(self) -> None:
"""关闭 gRPC 连接."""
if self._channel is not None:
await self._channel.close()
self._channel = None
"""建立 gRPC 连接并初始化 stub."""
await super().connect()
self._kg_stub = content_pb2_grpc.KnowledgeGraphServiceStub(self.channel)
self._q_stub = content_pb2_grpc.QuestionServiceStub(self.channel)
def is_available(self) -> bool:
return self._channel is not None
return self._channel is not None and self._kg_stub is not None
async def get_prerequisites(
self,
knowledge_point_id: str,
) -> list[KnowledgePoint]:
"""查询知识点前置依赖KnowledgeGraphService.GetPrerequisites."""
if not self.is_available():
logger.warning("content_client_not_connected_using_mock")
return await self._mock.get_prerequisites(knowledge_point_id)
try:
# 动态导入 proto 生成代码(如果存在)
# 全并行模式proto 未生成时降级到 mock
return await self._mock.get_prerequisites(knowledge_point_id)
except Exception as exc: # noqa: BLE001
logger.warning(
"content_get_prerequisites_failed_degraded",
error=str(exc),
raise AIError(
ErrorCode.AI_DOWNSTREAM_UNAVAILABLE,
"content client not connected",
)
return await self._mock.get_prerequisites(knowledge_point_id)
assert self._kg_stub is not None # noqa: S101 - narrowing for type checker
request = content_pb2.GetPrerequisitesRequest(
knowledge_point_id=knowledge_point_id,
depth=1,
)
try:
response: content_pb2.KnowledgePointsResponse = await self._kg_stub.GetPrerequisites(
request
)
except grpc.aio.AioRpcError as exc:
raise AIError(
ErrorCode.AI_DOWNSTREAM_UNAVAILABLE,
f"content.GetPrerequisites failed: {exc.details()}",
) from exc
return [KnowledgePoint(id=p.id, title=p.title) for p in response.points]
async def get_learning_path(
self,
student_id: str,
subject_id: str,
) -> list[KnowledgePoint]:
"""查询学习路径KnowledgeGraphService.GetLearningPath."""
if not self.is_available():
return await self._mock.get_learning_path(student_id, subject_id)
try:
return await self._mock.get_learning_path(student_id, subject_id)
except Exception as exc: # noqa: BLE001
logger.warning(
"content_get_learning_path_failed_degraded",
error=str(exc),
raise AIError(
ErrorCode.AI_DOWNSTREAM_UNAVAILABLE,
"content client not connected",
)
return await self._mock.get_learning_path(student_id, subject_id)
assert self._kg_stub is not None # noqa: S101
request = content_pb2.GetLearningPathRequest(
student_id=student_id,
subject_id=subject_id,
)
try:
response: content_pb2.LearningPath = await self._kg_stub.GetLearningPath(request)
except grpc.aio.AioRpcError as exc:
raise AIError(
ErrorCode.AI_DOWNSTREAM_UNAVAILABLE,
f"content.GetLearningPath failed: {exc.details()}",
) from exc
return [KnowledgePoint(id=p.id, title=p.title) for p in response.points]
async def create_questions(
self,
questions: list[QuestionInput],
user_id: str = "",
) -> list[CreatedQuestion]:
"""批量创建题目入库QuestionService.BatchCreateQuestions."""
if not self.is_available():
logger.warning("content_client_not_connected_using_mock")
return await self._mock.create_questions(questions, user_id)
try:
return await self._mock.create_questions(questions, user_id)
except Exception as exc: # noqa: BLE001
raise AIError(
ErrorCode.AI_DOWNSTREAM_UNAVAILABLE,
f"content.CreateQuestions failed: {exc}",
"content client not connected",
)
assert self._q_stub is not None # noqa: S101
proto_questions = [
content_pb2.CreateQuestionRequest(
knowledge_point_id=(q.knowledge_point_ids[0] if q.knowledge_point_ids else ""),
type=q.question_type,
content=q.question,
answer=q.answer,
explanation=q.explanation,
difficulty=_parse_difficulty(q.difficulty),
source="ai_workflow",
created_by=user_id,
)
for q in questions
]
request = content_pb2.BatchCreateQuestionsRequest(questions=proto_questions)
try:
response: content_pb2.BatchCreateQuestionsResponse = (
await self._q_stub.BatchCreateQuestions(request)
)
except grpc.aio.AioRpcError as exc:
raise AIError(
ErrorCode.AI_DOWNSTREAM_UNAVAILABLE,
f"content.BatchCreateQuestions failed: {exc.details()}",
) from exc
# 按 ids 顺序返回,过滤掉失败的索引
results: list[CreatedQuestion] = []
failed_indices = {f.index for f in response.failed}
for i, q in enumerate(questions):
if i in failed_indices:
continue
if i < len(response.ids):
results.append(CreatedQuestion(id=response.ids[i], question=q.question))
return results
def _parse_difficulty(difficulty: str) -> int:
"""将字符串难度映射为 proto int32 difficulty."""
mapping = {"easy": 1, "medium": 2, "hard": 3, "简单": 1, "中等": 2, "困难": 3}
return mapping.get(difficulty.lower() if difficulty else "", 2)

View File

@@ -1,19 +1,23 @@
"""Data-ana 服务 gRPC 客户端.
用于:
- 查询班级学情GetClassPerformance
- 查询学生薄弱点GetStudentWeakness
- 查询学习趋势GetLearningTrend
- 查询班级学情(AnalyticsService.GetClassPerformance
- 查询学生薄弱点(AnalyticsService.GetStudentWeakness
- 查询学习趋势(AnalyticsService.GetLearningTrend
全并行模式:下游不可用时使用 mock 数据降级
真实 gRPC 调用:未连接或调用失败时抛 AIError不再降级到 mock 数据
"""
from abc import ABC, abstractmethod
from dataclasses import dataclass, field
from typing import Any
import grpc
import structlog
from ..errors import AIError, ErrorCode
from ..proto_gen import analytics_pb2, analytics_pb2_grpc
from .base_client import BaseGrpcClient
logger = structlog.get_logger()
@@ -109,7 +113,7 @@ class DataAnaClient(ABC):
class DataAnaClientMock(DataAnaClient):
"""Data-ana 客户端 Mock 实现(全并行模式."""
"""Data-ana 客户端 Mock 实现(仅用于单元测试."""
def __init__(self) -> None:
self._available = True
@@ -121,11 +125,6 @@ class DataAnaClientMock(DataAnaClient):
start_date: int = 0,
end_date: int = 0,
) -> ClassPerformance:
logger.info(
"data_ana_mock_class_performance",
class_id=class_id,
subject_id=subject_id,
)
return ClassPerformance(
class_id=class_id,
average_score=78.5,
@@ -133,7 +132,6 @@ class DataAnaClientMock(DataAnaClient):
scores=[
StudentScore(student_id="s_001", score=85.0, grade="A"),
StudentScore(student_id="s_002", score=72.0, grade="B"),
StudentScore(student_id="s_003", score=65.0, grade="C"),
],
)
@@ -142,11 +140,6 @@ class DataAnaClientMock(DataAnaClient):
student_id: str,
subject_id: str,
) -> StudentWeakness:
logger.info(
"data_ana_mock_student_weakness",
student_id=student_id,
subject_id=subject_id,
)
return StudentWeakness(
student_id=student_id,
weak_points=[
@@ -155,11 +148,6 @@ class DataAnaClientMock(DataAnaClient):
title="函数概念mock",
mastery=0.45,
),
WeakPoint(
knowledge_point_id="kp_005",
title="三角函数mock",
mastery=0.52,
),
],
)
@@ -169,16 +157,11 @@ class DataAnaClientMock(DataAnaClient):
start_date: int = 0,
end_date: int = 0,
) -> LearningTrend:
logger.info(
"data_ana_mock_learning_trend",
student_id=student_id,
)
return LearningTrend(
student_id=student_id,
points=[
TrendPoint(date=20260101, score=65.0),
TrendPoint(date=20260201, score=70.0),
TrendPoint(date=20260301, score=75.0),
],
)
@@ -186,37 +169,24 @@ class DataAnaClientMock(DataAnaClient):
return self._available
class DataAnaClientGrpc(DataAnaClient):
"""Data-ana 服务 gRPC 客户端实现.
全并行模式gRPC 调用失败时降级到 mock 数据。
"""
class DataAnaClientGrpc(BaseGrpcClient, DataAnaClient):
"""Data-ana 服务 gRPC 客户端实现(真实调用,不降级到 mock."""
def __init__(
self,
endpoint: str = "localhost:50055",
request_id: str = "",
) -> None:
self._endpoint = endpoint
self._request_id = request_id
self._channel: Any = None
self._mock = DataAnaClientMock()
super().__init__(endpoint, request_id)
self._stub: analytics_pb2_grpc.AnalyticsServiceStub | None = None
async def connect(self) -> None:
"""建立 gRPC 连接."""
import grpc
self._channel = grpc.aio.insecure_channel(self._endpoint)
logger.info("data_ana_client_connected", endpoint=self._endpoint)
async def close(self) -> None:
"""关闭 gRPC 连接."""
if self._channel is not None:
await self._channel.close()
self._channel = None
"""建立 gRPC 连接并初始化 stub."""
await super().connect()
self._stub = analytics_pb2_grpc.AnalyticsServiceStub(self.channel)
def is_available(self) -> bool:
return self._channel is not None
return self._channel is not None and self._stub is not None
async def get_class_performance(
self,
@@ -225,39 +195,74 @@ class DataAnaClientGrpc(DataAnaClient):
start_date: int = 0,
end_date: int = 0,
) -> ClassPerformance:
"""查询班级学情AnalyticsService.GetClassPerformance."""
if not self.is_available():
return await self._mock.get_class_performance(
class_id, subject_id, start_date, end_date,
raise AIError(
ErrorCode.AI_DOWNSTREAM_UNAVAILABLE,
"data-ana client not connected",
)
assert self._stub is not None # noqa: S101
request = analytics_pb2.GetClassPerformanceRequest(
class_id=class_id,
subject_id=subject_id,
start_date=start_date,
end_date=end_date,
)
try:
# 全并行模式proto 未生成时降级到 mock
return await self._mock.get_class_performance(
class_id, subject_id, start_date, end_date,
)
except Exception as exc: # noqa: BLE001
logger.warning(
"data_ana_class_performance_failed_degraded",
error=str(exc),
)
return await self._mock.get_class_performance(
class_id, subject_id, start_date, end_date,
)
response: analytics_pb2.ClassPerformance = await self._stub.GetClassPerformance(request)
except grpc.aio.AioRpcError as exc:
raise AIError(
ErrorCode.AI_DOWNSTREAM_UNAVAILABLE,
f"data-ana.GetClassPerformance failed: {exc.details()}",
) from exc
return ClassPerformance(
class_id=response.class_id,
average_score=response.average_score,
pass_rate=response.pass_rate,
scores=[
StudentScore(
student_id=s.student_id,
score=s.score,
grade=s.grade,
)
for s in response.scores
],
)
async def get_student_weakness(
self,
student_id: str,
subject_id: str,
) -> StudentWeakness:
"""查询学生薄弱点AnalyticsService.GetStudentWeakness."""
if not self.is_available():
return await self._mock.get_student_weakness(student_id, subject_id)
try:
return await self._mock.get_student_weakness(student_id, subject_id)
except Exception as exc: # noqa: BLE001
logger.warning(
"data_ana_student_weakness_failed_degraded",
error=str(exc),
raise AIError(
ErrorCode.AI_DOWNSTREAM_UNAVAILABLE,
"data-ana client not connected",
)
return await self._mock.get_student_weakness(student_id, subject_id)
assert self._stub is not None # noqa: S101
request = analytics_pb2.GetStudentWeaknessRequest(
student_id=student_id,
subject_id=subject_id,
)
try:
response: analytics_pb2.StudentWeakness = await self._stub.GetStudentWeakness(request)
except grpc.aio.AioRpcError as exc:
raise AIError(
ErrorCode.AI_DOWNSTREAM_UNAVAILABLE,
f"data-ana.GetStudentWeakness failed: {exc.details()}",
) from exc
return StudentWeakness(
student_id=response.student_id,
weak_points=[
WeakPoint(
knowledge_point_id=p.knowledge_point_id,
title=p.title,
mastery=p.mastery,
)
for p in response.weak_points
],
)
async def get_learning_trend(
self,
@@ -265,19 +270,26 @@ class DataAnaClientGrpc(DataAnaClient):
start_date: int = 0,
end_date: int = 0,
) -> LearningTrend:
"""查询学习趋势AnalyticsService.GetLearningTrend."""
if not self.is_available():
return await self._mock.get_learning_trend(
student_id, start_date, end_date,
raise AIError(
ErrorCode.AI_DOWNSTREAM_UNAVAILABLE,
"data-ana client not connected",
)
assert self._stub is not None # noqa: S101
request = analytics_pb2.GetLearningTrendRequest(
student_id=student_id,
start_date=start_date,
end_date=end_date,
)
try:
return await self._mock.get_learning_trend(
student_id, start_date, end_date,
)
except Exception as exc: # noqa: BLE001
logger.warning(
"data_ana_learning_trend_failed_degraded",
error=str(exc),
)
return await self._mock.get_learning_trend(
student_id, start_date, end_date,
)
response: analytics_pb2.LearningTrend = await self._stub.GetLearningTrend(request)
except grpc.aio.AioRpcError as exc:
raise AIError(
ErrorCode.AI_DOWNSTREAM_UNAVAILABLE,
f"data-ana.GetLearningTrend failed: {exc.details()}",
) from exc
return LearningTrend(
student_id=response.student_id,
points=[TrendPoint(date=p.date, score=p.score) for p in response.points],
)

View File

@@ -1,17 +1,22 @@
"""IAM 服务 gRPC 客户端.
用于:
- 查询用户有效数据范围GetEffectiveDataScope - ISSUE-07: P4 补全ai 用 mock
- 查询用户有效数据范围(IamService.GetEffectiveDataScope
全并行模式IAM 不可用时使用 mock 数据降级
真实 gRPC 调用:未连接或调用失败时抛 AIError不再降级到 mock 数据
"""
from abc import ABC, abstractmethod
from dataclasses import dataclass
from typing import Any
import grpc
import structlog
from ..errors import AIError, ErrorCode
from ..proto_gen import iam_pb2, iam_pb2_grpc
from .base_client import BaseGrpcClient
logger = structlog.get_logger()
@@ -56,10 +61,7 @@ class IamClient(ABC):
self,
user_id: str,
) -> DataScope:
"""查询用户有效数据范围.
ISSUE-07: IAM P4 补全 GetEffectiveDataScope RPC 后启用真实调用。
"""
"""查询用户有效数据范围."""
...
@abstractmethod
@@ -69,7 +71,7 @@ class IamClient(ABC):
class IamClientMock(IamClient):
"""IAM 客户端 Mock 实现(全并行模式."""
"""IAM 客户端 Mock 实现(仅用于单元测试."""
def __init__(self) -> None:
self._available = True
@@ -78,7 +80,6 @@ class IamClientMock(IamClient):
self,
user_id: str,
) -> DataScope:
logger.info("iam_mock_get_data_scope", user_id=user_id)
return DataScope(
user_id=user_id,
school_id="school_mock_001",
@@ -93,43 +94,68 @@ class IamClientMock(IamClient):
return self._available
class IamClientGrpc(IamClient):
"""IAM 服务 gRPC 客户端实现.
ISSUE-07: IAM P4 补全 GetEffectiveDataScope RPC 后启用。
全并行模式:当前使用 mock 数据。
"""
class IamClientGrpc(BaseGrpcClient, IamClient):
"""IAM 服务 gRPC 客户端实现(真实调用,不降级到 mock."""
def __init__(
self,
endpoint: str = "localhost:50052",
request_id: str = "",
) -> None:
self._endpoint = endpoint
self._request_id = request_id
self._channel: Any = None
self._mock = IamClientMock()
super().__init__(endpoint, request_id)
self._stub: iam_pb2_grpc.IamServiceStub | None = None
async def connect(self) -> None:
"""建立 gRPC 连接."""
import grpc
self._channel = grpc.aio.insecure_channel(self._endpoint)
logger.info("iam_client_connected", endpoint=self._endpoint)
async def close(self) -> None:
"""关闭 gRPC 连接."""
if self._channel is not None:
await self._channel.close()
self._channel = None
"""建立 gRPC 连接并初始化 stub."""
await super().connect()
self._stub = iam_pb2_grpc.IamServiceStub(self.channel)
def is_available(self) -> bool:
return self._channel is not None
return self._channel is not None and self._stub is not None
async def get_effective_data_scope(
self,
user_id: str,
) -> DataScope:
# ISSUE-07: IAM P4 补全 GetEffectiveDataScope 后启用真实调用
# 全并行模式:当前使用 mock
return await self._mock.get_effective_data_scope(user_id)
"""查询用户有效数据范围IamService.GetEffectiveDataScope.
Proto 返回 EffectiveDataScope{user_id, level, scope_ids, school_id}
- level=SELF → class_ids=[], 仅本人
- level=CLASS → class_ids=scope_ids
- level=GRADE → grade_ids=scope_ids
- level=SCHOOL → school_id 取响应的 school_id
- level=ALL → is_admin=True
"""
if not self.is_available():
raise AIError(
ErrorCode.AI_DOWNSTREAM_UNAVAILABLE,
"iam client not connected",
)
assert self._stub is not None # noqa: S101
request = iam_pb2.GetEffectiveDataScopeRequest(user_id=user_id)
try:
response: iam_pb2.EffectiveDataScope = await self._stub.GetEffectiveDataScope(request)
except grpc.aio.AioRpcError as exc:
raise AIError(
ErrorCode.AI_DOWNSTREAM_UNAVAILABLE,
f"iam.GetEffectiveDataScope failed: {exc.details()}",
) from exc
# 将 proto EffectiveDataScope 映射为内部 DataScope
class_ids: list[str] = []
grade_ids: list[str] = []
is_admin = False
level = response.level.upper() if response.level else "SELF"
if level == "CLASS":
class_ids = list(response.scope_ids)
elif level == "GRADE":
grade_ids = list(response.scope_ids)
elif level == "ALL":
is_admin = True
return DataScope(
user_id=response.user_id or user_id,
school_id=response.school_id,
class_ids=class_ids,
grade_ids=grade_ids,
role="admin" if is_admin else "teacher",
is_admin=is_admin,
)

View File

@@ -1,13 +1,15 @@
"""gRPC 拦截器.
"""gRPC 拦截器(异步版本,适配 grpc.aio.server.
提供:
- LoggingInterceptor: 请求/响应日志 + 延迟统计
- ErrorInterceptor: 异常捕获 → gRPC status code 映射
- AuthInterceptor: 从 metadata 提取用户上下文
注意grpc.aio.server 的 interceptors 必须继承 grpc.aio.ServerInterceptor
且 intercept_service 必须是 async 方法。同步 grpc.ServerInterceptor 会触发 ValueError。
"""
import time
from collections.abc import Callable
from typing import Any
import grpc
@@ -20,66 +22,72 @@ from ..middleware.error_handler import grpc_error_mapper
logger = structlog.get_logger()
class LoggingInterceptor(grpc.ServerInterceptor):
"""请求日志 + 延迟统计."""
async def _async_wrapper(
handler: Any,
request: Any,
context: grpc.aio.ServicerContext,
) -> Any:
"""调用 async 或 sync handler 并返回响应."""
return await handler(request, context)
def intercept_service(
class LoggingInterceptor(grpc.aio.ServerInterceptor):
"""请求日志 + 延迟统计(异步)."""
async def intercept_service(
self,
continuation: Callable[[grpc.HandlerCallDetails], grpc.RpcMethodHandler],
continuation: Any,
handler_call_details: grpc.HandlerCallDetails,
) -> grpc.RpcMethodHandler:
method = handler_call_details.method
start = time.monotonic()
def log_wrapper(handler: grpc.RpcMethodHandler) -> grpc.RpcMethodHandler:
original_behavior = handler.unary_unary
def new_behavior(request: Any, context: grpc.ServicerContext) -> Any:
latency_ms = int((time.monotonic() - start) * 1000)
try:
response = original_behavior(request, context) # type: ignore[misc]
logger.info(
"grpc_request",
method=method,
latency_ms=latency_ms,
status="ok",
)
return response
except Exception as exc:
logger.error(
"grpc_request_error",
method=method,
latency_ms=latency_ms,
error=str(exc),
)
raise
handler.unary_unary = new_behavior # type: ignore[method-assign]
return handler
handler = continuation(handler_call_details)
if handler is None:
return None
return log_wrapper(handler)
class ErrorInterceptor(grpc.ServerInterceptor):
"""异常捕获 → gRPC status code 映射."""
def intercept_service(
self,
continuation: Callable[[grpc.HandlerCallDetails], grpc.RpcMethodHandler],
handler_call_details: grpc.HandlerCallDetails,
) -> grpc.RpcMethodHandler:
handler = continuation(handler_call_details)
handler = await continuation(handler_call_details)
if handler is None:
return None
original_behavior = handler.unary_unary
def new_behavior(request: Any, context: grpc.ServicerContext) -> Any:
async def new_behavior(request: Any, context: grpc.aio.ServicerContext) -> Any:
latency_ms = int((time.monotonic() - start) * 1000)
try:
return original_behavior(request, context) # type: ignore[misc]
response = await _async_wrapper(original_behavior, request, context)
logger.info(
"grpc_request",
method=method,
latency_ms=latency_ms,
status="ok",
)
return response
except Exception as exc:
logger.error(
"grpc_request_error",
method=method,
latency_ms=latency_ms,
error=str(exc),
)
raise
handler.unary_unary = new_behavior # type: ignore[method-assign]
return handler
class ErrorInterceptor(grpc.aio.ServerInterceptor):
"""异常捕获 → gRPC status code 映射(异步)."""
async def intercept_service(
self,
continuation: Any,
handler_call_details: grpc.HandlerCallDetails,
) -> grpc.RpcMethodHandler:
handler = await continuation(handler_call_details)
if handler is None:
return None
original_behavior = handler.unary_unary
async def new_behavior(request: Any, context: grpc.aio.ServicerContext) -> Any:
try:
return await _async_wrapper(original_behavior, request, context)
except AIError as exc:
code, msg, grpc_status = grpc_error_mapper(exc)
logger.warning(
@@ -88,7 +96,7 @@ class ErrorInterceptor(grpc.ServerInterceptor):
error_code=code,
message=msg,
)
context.abort(_grpc_status(grpc_status), f"{code}: {msg}")
await context.abort(_grpc_status(grpc_status), f"{code}: {msg}")
except Exception as exc:
code, msg, grpc_status = grpc_error_mapper(exc)
logger.error(
@@ -96,31 +104,33 @@ class ErrorInterceptor(grpc.ServerInterceptor):
method=handler_call_details.method,
error=str(exc),
)
context.abort(_grpc_status(grpc_status), f"{code}: {msg}")
await context.abort(_grpc_status(grpc_status), f"{code}: {msg}")
handler.unary_unary = new_behavior # type: ignore[method-assign]
return handler
class AuthInterceptor(grpc.ServerInterceptor):
"""从 gRPC metadata 提取用户上下文,存入 context."""
class AuthInterceptor(grpc.aio.ServerInterceptor):
"""从 gRPC metadata 提取用户上下文,存入 context(异步)."""
def intercept_service(
async def intercept_service(
self,
continuation: Callable[[grpc.HandlerCallDetails], grpc.RpcMethodHandler],
continuation: Any,
handler_call_details: grpc.HandlerCallDetails,
) -> grpc.RpcMethodHandler:
handler = continuation(handler_call_details)
handler = await continuation(handler_call_details)
if handler is None:
return None
original_behavior = handler.unary_unary
def new_behavior(request: Any, context: grpc.ServicerContext) -> Any:
ctx = extract_user_context_from_metadata(handler_call_details.invocation_metadata)
async def new_behavior(request: Any, context: grpc.aio.ServicerContext) -> Any:
ctx = extract_user_context_from_metadata(
handler_call_details.invocation_metadata,
)
# 将 UserContext 存入 context 供 servicer 使用
context.user_context = ctx # type: ignore[attr-defined]
return original_behavior(request, context) # type: ignore[misc]
return await _async_wrapper(original_behavior, request, context)
handler.unary_unary = new_behavior # type: ignore[method-assign]
return handler
@@ -149,6 +159,6 @@ def _grpc_status(code: int) -> grpc.StatusCode:
return status_map.get(code, grpc.StatusCode.UNKNOWN)
def get_user_context(context: grpc.ServicerContext) -> UserContext:
def get_user_context(context: grpc.aio.ServicerContext) -> UserContext:
"""从 ServicerContext 提取 UserContextAuthInterceptor 注入)."""
return getattr(context, "user_context", UserContext())

View File

@@ -39,16 +39,10 @@ class GrpcServer:
async def start(self) -> None:
"""启动 gRPC server.
注意:grpc.aio.server 的 interceptors 需要 grpc.aio.ServerInterceptor 基类,
当前拦截器使用同步 grpc.ServerInterceptor 基类会报 ValueError。
此处 try/except 降级为无拦截器启动,避免阻塞服务启动。
使用 grpc.aio.ServerInterceptor 异步拦截器Logging/Auth/Error
"""
interceptors = [LoggingInterceptor(), AuthInterceptor(), ErrorInterceptor()]
try:
self._server = grpc.aio.server(interceptors=interceptors)
except (ValueError, TypeError):
logger.warning("grpc_interceptors_incompatible_start_without")
self._server = grpc.aio.server()
self._server = grpc.aio.server(interceptors=interceptors)
ai_pb2_grpc.add_AiServiceServicer_to_server(self._servicer, self._server)
self._server.add_insecure_port(f"[::]:{self._port}")
await self._server.start()

View File

@@ -8,7 +8,7 @@
- 评估三道防线RuleValidator + LLMJudge + QualityGate
- 用量记录Redis+ Kafka 事件发布 + 配额管理
- 安全层PII + 输入清洗 + 输出审核)
- 下游 gRPC 客户端content/data-ana/iam全并行用 Mock
- 下游 gRPC 客户端content/data-ana/iam真实 gRPC 调用
- 备课工作流4 步编排 + Redis 状态存储)
- 限流Redis 三维度令牌桶)
- OpenTelemetry + Prometheus
@@ -30,7 +30,7 @@ from opentelemetry.sdk.trace.export import BatchSpanProcessor
from prometheus_client import make_asgi_app
from redis.asyncio import Redis
from .clients import ContentClientMock, DataAnaClientMock, IamClientMock
from .clients import ContentClientGrpc, DataAnaClientGrpc, IamClientGrpc
from .config import settings
from .grpc_server import create_grpc_server
from .middleware import (
@@ -130,10 +130,11 @@ _rate_limiter = RateLimiter(
school_limit=settings.redis_rate_limit_school_per_min,
)
# 下游客户端(全并行模式用 MockISSUE-07
_content_client = ContentClientMock()
_data_ana_client = DataAnaClientMock()
_iam_client = IamClientMock()
# 下游 gRPC 客户端(真实 gRPC 调用lifespan 中连接
# 连接失败时降级(不阻断启动),但调用未连接的客户端方法会抛 AIError
_content_client = ContentClientGrpc(endpoint=settings.content_grpc_endpoint)
_data_ana_client = DataAnaClientGrpc(endpoint=settings.data_ana_grpc_endpoint)
_iam_client = IamClientGrpc(endpoint=settings.iam_grpc_endpoint)
_state_store = WorkflowStateStore(
redis=None,
@@ -179,6 +180,22 @@ async def lifespan(app: FastAPI) -> AsyncGenerator[None, None]:
logger.warning("redis_connect_failed_degraded", error=str(exc))
_redis = None
# 下游 gRPC 客户端连接(每个独立 try单个失败不阻断其他
downstream_clients = [
("content", _content_client),
("data_ana", _data_ana_client),
("iam", _iam_client),
]
for name, client in downstream_clients:
try:
await client.connect()
except Exception as exc: # noqa: BLE001
logger.warning(
"downstream_client_connect_failed",
client=name,
error=str(exc),
)
await _kafka_producer.start()
await _grpc_server.start()
@@ -188,6 +205,9 @@ async def lifespan(app: FastAPI) -> AsyncGenerator[None, None]:
grpc_port=settings.grpc_port,
dev_mode=settings.is_dev,
llm_available=settings.llm_available,
content_connected=_content_client.is_available(),
data_ana_connected=_data_ana_client.is_available(),
iam_connected=_iam_client.is_available(),
)
if not settings.llm_available:
logger.warning("ai_service_llm_degraded_no_api_key")
@@ -198,6 +218,15 @@ async def lifespan(app: FastAPI) -> AsyncGenerator[None, None]:
logger.info("ai_service_stopping")
await _grpc_server.stop()
await _kafka_producer.stop()
for name, client in reversed(downstream_clients):
try:
await client.close()
except Exception as exc: # noqa: BLE001
logger.warning(
"downstream_client_close_failed",
client=name,
error=str(exc),
)
if _redis is not None:
await _redis.aclose()
logger.info("redis_closed")

View File

@@ -1,14 +1,46 @@
"""protobuf 生成代码(勿手动编辑).
由 grpc_tools.protoc 从 packages/shared-proto/proto/ai.proto 生成。
重新生成命令:
由 grpc_tools.protoc 从 packages/shared-proto/proto/ 生成。
重新生成命令(在 services/ai 目录下)
uv run python -m grpc_tools.protoc \\
-I ../../packages/shared-proto/proto \\
--python_out=src/ai/proto_gen \\
--grpc_python_out=src/ai/proto_gen \\
../../packages/shared-proto/proto/ai.proto
../../packages/shared-proto/proto/ai.proto \\
../../packages/shared-proto/proto/content.proto \\
../../packages/shared-proto/proto/analytics.proto \\
../../packages/shared-proto/proto/iam.proto
注意:生成的 *_pb2_grpc.py 文件使用绝对导入(如 `import content_pb2 as content__pb2`
本文件通过 sys.path 注入确保这些绝对导入可用。
"""
from . import ai_pb2, ai_pb2_grpc
import os
import sys
__all__ = ["ai_pb2", "ai_pb2_grpc"]
# 将本目录加入 sys.path使生成的 *_pb2_grpc.py 中的绝对导入(如 `import content_pb2`)可用
_PB_DIR = os.path.dirname(os.path.abspath(__file__))
if _PB_DIR not in sys.path:
sys.path.insert(0, _PB_DIR)
from . import ( # noqa: E402
ai_pb2,
ai_pb2_grpc,
analytics_pb2,
analytics_pb2_grpc,
content_pb2,
content_pb2_grpc,
iam_pb2,
iam_pb2_grpc,
)
__all__ = [
"ai_pb2",
"ai_pb2_grpc",
"analytics_pb2",
"analytics_pb2_grpc",
"content_pb2",
"content_pb2_grpc",
"iam_pb2",
"iam_pb2_grpc",
]

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,591 @@
# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT!
"""Client and server classes corresponding to protobuf-defined services."""
import grpc
import warnings
import analytics_pb2 as analytics__pb2
GRPC_GENERATED_VERSION = '1.82.1'
GRPC_VERSION = grpc.__version__
_version_not_supported = False
try:
from grpc._utilities import first_version_is_lower
_version_not_supported = first_version_is_lower(GRPC_VERSION, GRPC_GENERATED_VERSION)
except ImportError:
_version_not_supported = True
if _version_not_supported:
raise RuntimeError(
f'The grpc package installed is at version {GRPC_VERSION},'
+ ' but the generated code in analytics_pb2_grpc.py depends on'
+ f' grpcio>={GRPC_GENERATED_VERSION}.'
+ f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}'
+ f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.'
)
class AnalyticsServiceStub:
"""AnalyticsService 数据分析服务契约D6 智能洞察领域).
P4 启用 gRPC server 端口 50055HTTP 3006 保留作 Gateway 直连降级.
所有 RPC 返回 ActionState 信封success/data/error/details.degraded.
"""
def __init__(self, channel):
"""Constructor.
Args:
channel: A grpc.Channel.
"""
self.GetClassPerformance = channel.unary_unary(
'/next_edu_cloud.analytics.v1.AnalyticsService/GetClassPerformance',
request_serializer=analytics__pb2.GetClassPerformanceRequest.SerializeToString,
response_deserializer=analytics__pb2.ClassPerformance.FromString,
_registered_method=True)
self.GetStudentWeakness = channel.unary_unary(
'/next_edu_cloud.analytics.v1.AnalyticsService/GetStudentWeakness',
request_serializer=analytics__pb2.GetStudentWeaknessRequest.SerializeToString,
response_deserializer=analytics__pb2.StudentWeakness.FromString,
_registered_method=True)
self.GetLearningTrend = channel.unary_unary(
'/next_edu_cloud.analytics.v1.AnalyticsService/GetLearningTrend',
request_serializer=analytics__pb2.GetLearningTrendRequest.SerializeToString,
response_deserializer=analytics__pb2.LearningTrend.FromString,
_registered_method=True)
self.GetTeacherDashboard = channel.unary_unary(
'/next_edu_cloud.analytics.v1.AnalyticsService/GetTeacherDashboard',
request_serializer=analytics__pb2.GetTeacherDashboardRequest.SerializeToString,
response_deserializer=analytics__pb2.TeacherDashboard.FromString,
_registered_method=True)
self.GetStudentDashboard = channel.unary_unary(
'/next_edu_cloud.analytics.v1.AnalyticsService/GetStudentDashboard',
request_serializer=analytics__pb2.GetStudentDashboardRequest.SerializeToString,
response_deserializer=analytics__pb2.StudentDashboard.FromString,
_registered_method=True)
self.GetParentDashboard = channel.unary_unary(
'/next_edu_cloud.analytics.v1.AnalyticsService/GetParentDashboard',
request_serializer=analytics__pb2.GetParentDashboardRequest.SerializeToString,
response_deserializer=analytics__pb2.ParentDashboard.FromString,
_registered_method=True)
self.GetAdminDashboard = channel.unary_unary(
'/next_edu_cloud.analytics.v1.AnalyticsService/GetAdminDashboard',
request_serializer=analytics__pb2.GetAdminDashboardRequest.SerializeToString,
response_deserializer=analytics__pb2.AdminDashboard.FromString,
_registered_method=True)
self.GetWarnings = channel.unary_unary(
'/next_edu_cloud.analytics.v1.AnalyticsService/GetWarnings',
request_serializer=analytics__pb2.GetWarningsRequest.SerializeToString,
response_deserializer=analytics__pb2.WarningList.FromString,
_registered_method=True)
self.TriggerWarning = channel.unary_unary(
'/next_edu_cloud.analytics.v1.AnalyticsService/TriggerWarning',
request_serializer=analytics__pb2.TriggerWarningRequest.SerializeToString,
response_deserializer=analytics__pb2.TriggerWarningResponse.FromString,
_registered_method=True)
self.GetMasteryDistribution = channel.unary_unary(
'/next_edu_cloud.analytics.v1.AnalyticsService/GetMasteryDistribution',
request_serializer=analytics__pb2.GetMasteryDistributionRequest.SerializeToString,
response_deserializer=analytics__pb2.MasteryDistribution.FromString,
_registered_method=True)
self.GetStudentMastery = channel.unary_unary(
'/next_edu_cloud.analytics.v1.AnalyticsService/GetStudentMastery',
request_serializer=analytics__pb2.GetStudentMasteryRequest.SerializeToString,
response_deserializer=analytics__pb2.StudentMastery.FromString,
_registered_method=True)
self.SubscribeMasteryUpdate = channel.unary_stream(
'/next_edu_cloud.analytics.v1.AnalyticsService/SubscribeMasteryUpdate',
request_serializer=analytics__pb2.SubscribeMasteryUpdateRequest.SerializeToString,
response_deserializer=analytics__pb2.MasteryUpdateEvent.FromString,
_registered_method=True)
class AnalyticsServiceServicer:
"""AnalyticsService 数据分析服务契约D6 智能洞察领域).
P4 启用 gRPC server 端口 50055HTTP 3006 保留作 Gateway 直连降级.
所有 RPC 返回 ActionState 信封success/data/error/details.degraded.
"""
def GetClassPerformance(self, request, context):
"""班级成绩分析(平均分/及格率/参考人数).
"""
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
context.set_details('Method not implemented!')
raise NotImplementedError('Method not implemented!')
def GetStudentWeakness(self, request, context):
"""学生薄弱知识点mastery_level < 0.6.
"""
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
context.set_details('Method not implemented!')
raise NotImplementedError('Method not implemented!')
def GetLearningTrend(self, request, context):
"""学习趋势(历史成绩曲线).
"""
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
context.set_details('Method not implemented!')
raise NotImplementedError('Method not implemented!')
def GetTeacherDashboard(self, request, context):
"""教师仪表盘聚合(班级概览 + 待办 + 预警).
"""
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
context.set_details('Method not implemented!')
raise NotImplementedError('Method not implemented!')
def GetStudentDashboard(self, request, context):
"""学生仪表盘(个人学情 + 排名 + 薄弱点).
"""
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
context.set_details('Method not implemented!')
raise NotImplementedError('Method not implemented!')
def GetParentDashboard(self, request, context):
"""家长仪表盘(孩子学情概览).
"""
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
context.set_details('Method not implemented!')
raise NotImplementedError('Method not implemented!')
def GetAdminDashboard(self, request, context):
"""管理员仪表盘(全校统计 + AI 用量).
"""
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
context.set_details('Method not implemented!')
raise NotImplementedError('Method not implemented!')
def GetWarnings(self, request, context):
"""预警列表查询(按班级/严重度/时间过滤).
"""
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
context.set_details('Method not implemented!')
raise NotImplementedError('Method not implemented!')
def TriggerWarning(self, request, context):
"""手动触发预警(管理员/教师主动标记关注).
"""
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
context.set_details('Method not implemented!')
raise NotImplementedError('Method not implemented!')
def GetMasteryDistribution(self, request, context):
"""班级掌握度分布mastered/progressing/weak 三档).
"""
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
context.set_details('Method not implemented!')
raise NotImplementedError('Method not implemented!')
def GetStudentMastery(self, request, context):
"""学生知识点掌握度明细.
"""
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
context.set_details('Method not implemented!')
raise NotImplementedError('Method not implemented!')
def SubscribeMasteryUpdate(self, request, context):
"""订阅掌握度更新server-streamingP5+ AI 个性化推荐实时推送通道).
"""
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
context.set_details('Method not implemented!')
raise NotImplementedError('Method not implemented!')
def add_AnalyticsServiceServicer_to_server(servicer, server):
rpc_method_handlers = {
'GetClassPerformance': grpc.unary_unary_rpc_method_handler(
servicer.GetClassPerformance,
request_deserializer=analytics__pb2.GetClassPerformanceRequest.FromString,
response_serializer=analytics__pb2.ClassPerformance.SerializeToString,
),
'GetStudentWeakness': grpc.unary_unary_rpc_method_handler(
servicer.GetStudentWeakness,
request_deserializer=analytics__pb2.GetStudentWeaknessRequest.FromString,
response_serializer=analytics__pb2.StudentWeakness.SerializeToString,
),
'GetLearningTrend': grpc.unary_unary_rpc_method_handler(
servicer.GetLearningTrend,
request_deserializer=analytics__pb2.GetLearningTrendRequest.FromString,
response_serializer=analytics__pb2.LearningTrend.SerializeToString,
),
'GetTeacherDashboard': grpc.unary_unary_rpc_method_handler(
servicer.GetTeacherDashboard,
request_deserializer=analytics__pb2.GetTeacherDashboardRequest.FromString,
response_serializer=analytics__pb2.TeacherDashboard.SerializeToString,
),
'GetStudentDashboard': grpc.unary_unary_rpc_method_handler(
servicer.GetStudentDashboard,
request_deserializer=analytics__pb2.GetStudentDashboardRequest.FromString,
response_serializer=analytics__pb2.StudentDashboard.SerializeToString,
),
'GetParentDashboard': grpc.unary_unary_rpc_method_handler(
servicer.GetParentDashboard,
request_deserializer=analytics__pb2.GetParentDashboardRequest.FromString,
response_serializer=analytics__pb2.ParentDashboard.SerializeToString,
),
'GetAdminDashboard': grpc.unary_unary_rpc_method_handler(
servicer.GetAdminDashboard,
request_deserializer=analytics__pb2.GetAdminDashboardRequest.FromString,
response_serializer=analytics__pb2.AdminDashboard.SerializeToString,
),
'GetWarnings': grpc.unary_unary_rpc_method_handler(
servicer.GetWarnings,
request_deserializer=analytics__pb2.GetWarningsRequest.FromString,
response_serializer=analytics__pb2.WarningList.SerializeToString,
),
'TriggerWarning': grpc.unary_unary_rpc_method_handler(
servicer.TriggerWarning,
request_deserializer=analytics__pb2.TriggerWarningRequest.FromString,
response_serializer=analytics__pb2.TriggerWarningResponse.SerializeToString,
),
'GetMasteryDistribution': grpc.unary_unary_rpc_method_handler(
servicer.GetMasteryDistribution,
request_deserializer=analytics__pb2.GetMasteryDistributionRequest.FromString,
response_serializer=analytics__pb2.MasteryDistribution.SerializeToString,
),
'GetStudentMastery': grpc.unary_unary_rpc_method_handler(
servicer.GetStudentMastery,
request_deserializer=analytics__pb2.GetStudentMasteryRequest.FromString,
response_serializer=analytics__pb2.StudentMastery.SerializeToString,
),
'SubscribeMasteryUpdate': grpc.unary_stream_rpc_method_handler(
servicer.SubscribeMasteryUpdate,
request_deserializer=analytics__pb2.SubscribeMasteryUpdateRequest.FromString,
response_serializer=analytics__pb2.MasteryUpdateEvent.SerializeToString,
),
}
generic_handler = grpc.method_handlers_generic_handler(
'next_edu_cloud.analytics.v1.AnalyticsService', rpc_method_handlers)
server.add_generic_rpc_handlers((generic_handler,))
server.add_registered_method_handlers('next_edu_cloud.analytics.v1.AnalyticsService', rpc_method_handlers)
# This class is part of an EXPERIMENTAL API.
class AnalyticsService:
"""AnalyticsService 数据分析服务契约D6 智能洞察领域).
P4 启用 gRPC server 端口 50055HTTP 3006 保留作 Gateway 直连降级.
所有 RPC 返回 ActionState 信封success/data/error/details.degraded.
"""
@staticmethod
def GetClassPerformance(request,
target,
options=(),
channel_credentials=None,
call_credentials=None,
insecure=False,
compression=None,
wait_for_ready=None,
timeout=None,
metadata=None):
return grpc.experimental.unary_unary(
request,
target,
'/next_edu_cloud.analytics.v1.AnalyticsService/GetClassPerformance',
analytics__pb2.GetClassPerformanceRequest.SerializeToString,
analytics__pb2.ClassPerformance.FromString,
options,
channel_credentials,
insecure,
call_credentials,
compression,
wait_for_ready,
timeout,
metadata,
_registered_method=True)
@staticmethod
def GetStudentWeakness(request,
target,
options=(),
channel_credentials=None,
call_credentials=None,
insecure=False,
compression=None,
wait_for_ready=None,
timeout=None,
metadata=None):
return grpc.experimental.unary_unary(
request,
target,
'/next_edu_cloud.analytics.v1.AnalyticsService/GetStudentWeakness',
analytics__pb2.GetStudentWeaknessRequest.SerializeToString,
analytics__pb2.StudentWeakness.FromString,
options,
channel_credentials,
insecure,
call_credentials,
compression,
wait_for_ready,
timeout,
metadata,
_registered_method=True)
@staticmethod
def GetLearningTrend(request,
target,
options=(),
channel_credentials=None,
call_credentials=None,
insecure=False,
compression=None,
wait_for_ready=None,
timeout=None,
metadata=None):
return grpc.experimental.unary_unary(
request,
target,
'/next_edu_cloud.analytics.v1.AnalyticsService/GetLearningTrend',
analytics__pb2.GetLearningTrendRequest.SerializeToString,
analytics__pb2.LearningTrend.FromString,
options,
channel_credentials,
insecure,
call_credentials,
compression,
wait_for_ready,
timeout,
metadata,
_registered_method=True)
@staticmethod
def GetTeacherDashboard(request,
target,
options=(),
channel_credentials=None,
call_credentials=None,
insecure=False,
compression=None,
wait_for_ready=None,
timeout=None,
metadata=None):
return grpc.experimental.unary_unary(
request,
target,
'/next_edu_cloud.analytics.v1.AnalyticsService/GetTeacherDashboard',
analytics__pb2.GetTeacherDashboardRequest.SerializeToString,
analytics__pb2.TeacherDashboard.FromString,
options,
channel_credentials,
insecure,
call_credentials,
compression,
wait_for_ready,
timeout,
metadata,
_registered_method=True)
@staticmethod
def GetStudentDashboard(request,
target,
options=(),
channel_credentials=None,
call_credentials=None,
insecure=False,
compression=None,
wait_for_ready=None,
timeout=None,
metadata=None):
return grpc.experimental.unary_unary(
request,
target,
'/next_edu_cloud.analytics.v1.AnalyticsService/GetStudentDashboard',
analytics__pb2.GetStudentDashboardRequest.SerializeToString,
analytics__pb2.StudentDashboard.FromString,
options,
channel_credentials,
insecure,
call_credentials,
compression,
wait_for_ready,
timeout,
metadata,
_registered_method=True)
@staticmethod
def GetParentDashboard(request,
target,
options=(),
channel_credentials=None,
call_credentials=None,
insecure=False,
compression=None,
wait_for_ready=None,
timeout=None,
metadata=None):
return grpc.experimental.unary_unary(
request,
target,
'/next_edu_cloud.analytics.v1.AnalyticsService/GetParentDashboard',
analytics__pb2.GetParentDashboardRequest.SerializeToString,
analytics__pb2.ParentDashboard.FromString,
options,
channel_credentials,
insecure,
call_credentials,
compression,
wait_for_ready,
timeout,
metadata,
_registered_method=True)
@staticmethod
def GetAdminDashboard(request,
target,
options=(),
channel_credentials=None,
call_credentials=None,
insecure=False,
compression=None,
wait_for_ready=None,
timeout=None,
metadata=None):
return grpc.experimental.unary_unary(
request,
target,
'/next_edu_cloud.analytics.v1.AnalyticsService/GetAdminDashboard',
analytics__pb2.GetAdminDashboardRequest.SerializeToString,
analytics__pb2.AdminDashboard.FromString,
options,
channel_credentials,
insecure,
call_credentials,
compression,
wait_for_ready,
timeout,
metadata,
_registered_method=True)
@staticmethod
def GetWarnings(request,
target,
options=(),
channel_credentials=None,
call_credentials=None,
insecure=False,
compression=None,
wait_for_ready=None,
timeout=None,
metadata=None):
return grpc.experimental.unary_unary(
request,
target,
'/next_edu_cloud.analytics.v1.AnalyticsService/GetWarnings',
analytics__pb2.GetWarningsRequest.SerializeToString,
analytics__pb2.WarningList.FromString,
options,
channel_credentials,
insecure,
call_credentials,
compression,
wait_for_ready,
timeout,
metadata,
_registered_method=True)
@staticmethod
def TriggerWarning(request,
target,
options=(),
channel_credentials=None,
call_credentials=None,
insecure=False,
compression=None,
wait_for_ready=None,
timeout=None,
metadata=None):
return grpc.experimental.unary_unary(
request,
target,
'/next_edu_cloud.analytics.v1.AnalyticsService/TriggerWarning',
analytics__pb2.TriggerWarningRequest.SerializeToString,
analytics__pb2.TriggerWarningResponse.FromString,
options,
channel_credentials,
insecure,
call_credentials,
compression,
wait_for_ready,
timeout,
metadata,
_registered_method=True)
@staticmethod
def GetMasteryDistribution(request,
target,
options=(),
channel_credentials=None,
call_credentials=None,
insecure=False,
compression=None,
wait_for_ready=None,
timeout=None,
metadata=None):
return grpc.experimental.unary_unary(
request,
target,
'/next_edu_cloud.analytics.v1.AnalyticsService/GetMasteryDistribution',
analytics__pb2.GetMasteryDistributionRequest.SerializeToString,
analytics__pb2.MasteryDistribution.FromString,
options,
channel_credentials,
insecure,
call_credentials,
compression,
wait_for_ready,
timeout,
metadata,
_registered_method=True)
@staticmethod
def GetStudentMastery(request,
target,
options=(),
channel_credentials=None,
call_credentials=None,
insecure=False,
compression=None,
wait_for_ready=None,
timeout=None,
metadata=None):
return grpc.experimental.unary_unary(
request,
target,
'/next_edu_cloud.analytics.v1.AnalyticsService/GetStudentMastery',
analytics__pb2.GetStudentMasteryRequest.SerializeToString,
analytics__pb2.StudentMastery.FromString,
options,
channel_credentials,
insecure,
call_credentials,
compression,
wait_for_ready,
timeout,
metadata,
_registered_method=True)
@staticmethod
def SubscribeMasteryUpdate(request,
target,
options=(),
channel_credentials=None,
call_credentials=None,
insecure=False,
compression=None,
wait_for_ready=None,
timeout=None,
metadata=None):
return grpc.experimental.unary_stream(
request,
target,
'/next_edu_cloud.analytics.v1.AnalyticsService/SubscribeMasteryUpdate',
analytics__pb2.SubscribeMasteryUpdateRequest.SerializeToString,
analytics__pb2.MasteryUpdateEvent.FromString,
options,
channel_credentials,
insecure,
call_credentials,
compression,
wait_for_ready,
timeout,
metadata,
_registered_method=True)

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,719 @@
# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT!
"""Client and server classes corresponding to protobuf-defined services."""
import grpc
import warnings
import iam_pb2 as iam__pb2
GRPC_GENERATED_VERSION = '1.82.1'
GRPC_VERSION = grpc.__version__
_version_not_supported = False
try:
from grpc._utilities import first_version_is_lower
_version_not_supported = first_version_is_lower(GRPC_VERSION, GRPC_GENERATED_VERSION)
except ImportError:
_version_not_supported = True
if _version_not_supported:
raise RuntimeError(
f'The grpc package installed is at version {GRPC_VERSION},'
+ ' but the generated code in iam_pb2_grpc.py depends on'
+ f' grpcio>={GRPC_GENERATED_VERSION}.'
+ f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}'
+ f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.'
)
class IamServiceStub:
"""IamService 定义身份与访问管理契约
双入口策略president §2.16REST 供 gateway 透传 + admin-portal 直连,
gRPC 供 BFF 聚合调用。同一 Application Service 同时被两种 Controller 调用。
gRPC 端口 50052P2 即启用I1 裁决)。
"""
def __init__(self, channel):
"""Constructor.
Args:
channel: A grpc.Channel.
"""
self.Register = channel.unary_unary(
'/next_edu_cloud.iam.v1.IamService/Register',
request_serializer=iam__pb2.RegisterRequest.SerializeToString,
response_deserializer=iam__pb2.AuthResponse.FromString,
_registered_method=True)
self.Login = channel.unary_unary(
'/next_edu_cloud.iam.v1.IamService/Login',
request_serializer=iam__pb2.LoginRequest.SerializeToString,
response_deserializer=iam__pb2.AuthResponse.FromString,
_registered_method=True)
self.RefreshToken = channel.unary_unary(
'/next_edu_cloud.iam.v1.IamService/RefreshToken',
request_serializer=iam__pb2.RefreshTokenRequest.SerializeToString,
response_deserializer=iam__pb2.TokenPair.FromString,
_registered_method=True)
self.Logout = channel.unary_unary(
'/next_edu_cloud.iam.v1.IamService/Logout',
request_serializer=iam__pb2.LogoutRequest.SerializeToString,
response_deserializer=iam__pb2.LogoutResponse.FromString,
_registered_method=True)
self.GetUserInfo = channel.unary_unary(
'/next_edu_cloud.iam.v1.IamService/GetUserInfo',
request_serializer=iam__pb2.GetUserInfoRequest.SerializeToString,
response_deserializer=iam__pb2.UserInfo.FromString,
_registered_method=True)
self.GetUserProfile = channel.unary_unary(
'/next_edu_cloud.iam.v1.IamService/GetUserProfile',
request_serializer=iam__pb2.GetUserProfileRequest.SerializeToString,
response_deserializer=iam__pb2.UserInfo.FromString,
_registered_method=True)
self.UpdateProfile = channel.unary_unary(
'/next_edu_cloud.iam.v1.IamService/UpdateProfile',
request_serializer=iam__pb2.UpdateProfileRequest.SerializeToString,
response_deserializer=iam__pb2.UserInfo.FromString,
_registered_method=True)
self.ChangePassword = channel.unary_unary(
'/next_edu_cloud.iam.v1.IamService/ChangePassword',
request_serializer=iam__pb2.ChangePasswordRequest.SerializeToString,
response_deserializer=iam__pb2.ChangePasswordResponse.FromString,
_registered_method=True)
self.BatchGetUsers = channel.unary_unary(
'/next_edu_cloud.iam.v1.IamService/BatchGetUsers',
request_serializer=iam__pb2.BatchGetUsersRequest.SerializeToString,
response_deserializer=iam__pb2.BatchGetUsersResponse.FromString,
_registered_method=True)
self.GetEffectivePermissions = channel.unary_unary(
'/next_edu_cloud.iam.v1.IamService/GetEffectivePermissions',
request_serializer=iam__pb2.GetEffectivePermissionsRequest.SerializeToString,
response_deserializer=iam__pb2.EffectivePermissionsResponse.FromString,
_registered_method=True)
self.GetEffectiveAccess = channel.unary_unary(
'/next_edu_cloud.iam.v1.IamService/GetEffectiveAccess',
request_serializer=iam__pb2.GetEffectiveAccessRequest.SerializeToString,
response_deserializer=iam__pb2.EffectiveAccessResponse.FromString,
_registered_method=True)
self.GetEffectiveDataScope = channel.unary_unary(
'/next_edu_cloud.iam.v1.IamService/GetEffectiveDataScope',
request_serializer=iam__pb2.GetEffectiveDataScopeRequest.SerializeToString,
response_deserializer=iam__pb2.EffectiveDataScope.FromString,
_registered_method=True)
self.GetViewports = channel.unary_unary(
'/next_edu_cloud.iam.v1.IamService/GetViewports',
request_serializer=iam__pb2.GetViewportsRequest.SerializeToString,
response_deserializer=iam__pb2.ViewportsResponse.FromString,
_registered_method=True)
self.GetPublicKey = channel.unary_unary(
'/next_edu_cloud.iam.v1.IamService/GetPublicKey',
request_serializer=iam__pb2.GetPublicKeyRequest.SerializeToString,
response_deserializer=iam__pb2.PublicKeyResponse.FromString,
_registered_method=True)
self.GetChildrenByParent = channel.unary_unary(
'/next_edu_cloud.iam.v1.IamService/GetChildrenByParent',
request_serializer=iam__pb2.GetChildrenByParentRequest.SerializeToString,
response_deserializer=iam__pb2.ChildrenResponse.FromString,
_registered_method=True)
class IamServiceServicer:
"""IamService 定义身份与访问管理契约
双入口策略president §2.16REST 供 gateway 透传 + admin-portal 直连,
gRPC 供 BFF 聚合调用。同一 Application Service 同时被两种 Controller 调用。
gRPC 端口 50052P2 即启用I1 裁决)。
"""
def Register(self, request, context):
"""认证类
"""
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
context.set_details('Method not implemented!')
raise NotImplementedError('Method not implemented!')
def Login(self, request, context):
"""Missing associated documentation comment in .proto file."""
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
context.set_details('Method not implemented!')
raise NotImplementedError('Method not implemented!')
def RefreshToken(self, request, context):
"""Missing associated documentation comment in .proto file."""
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
context.set_details('Method not implemented!')
raise NotImplementedError('Method not implemented!')
def Logout(self, request, context):
"""Missing associated documentation comment in .proto file."""
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
context.set_details('Method not implemented!')
raise NotImplementedError('Method not implemented!')
def GetUserInfo(self, request, context):
"""用户信息类
"""
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
context.set_details('Method not implemented!')
raise NotImplementedError('Method not implemented!')
def GetUserProfile(self, request, context):
"""GetUserProfile 是 GetUserInfo 的语义别名student-bff 期望的命名).
返回结构与 GetUserInfo 完全相同,仅 RPC 名不同以兼容下游契约.
"""
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
context.set_details('Method not implemented!')
raise NotImplementedError('Method not implemented!')
def UpdateProfile(self, request, context):
"""Missing associated documentation comment in .proto file."""
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
context.set_details('Method not implemented!')
raise NotImplementedError('Method not implemented!')
def ChangePassword(self, request, context):
"""Missing associated documentation comment in .proto file."""
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
context.set_details('Method not implemented!')
raise NotImplementedError('Method not implemented!')
def BatchGetUsers(self, request, context):
"""Missing associated documentation comment in .proto file."""
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
context.set_details('Method not implemented!')
raise NotImplementedError('Method not implemented!')
def GetEffectivePermissions(self, request, context):
"""权限与视口类
"""
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
context.set_details('Method not implemented!')
raise NotImplementedError('Method not implemented!')
def GetEffectiveAccess(self, request, context):
"""Missing associated documentation comment in .proto file."""
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
context.set_details('Method not implemented!')
raise NotImplementedError('Method not implemented!')
def GetEffectiveDataScope(self, request, context):
"""GetEffectiveDataScope 解析用户可见数据范围DataScope 6 级).
data-ana gRPC 调用此 RPC 解析查询过滤范围coord-cross-review §2 #3 裁决 P4 补全).
"""
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
context.set_details('Method not implemented!')
raise NotImplementedError('Method not implemented!')
def GetViewports(self, request, context):
"""Missing associated documentation comment in .proto file."""
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
context.set_details('Method not implemented!')
raise NotImplementedError('Method not implemented!')
def GetPublicKey(self, request, context):
"""密钥与关系类
"""
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
context.set_details('Method not implemented!')
raise NotImplementedError('Method not implemented!')
def GetChildrenByParent(self, request, context):
"""Missing associated documentation comment in .proto file."""
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
context.set_details('Method not implemented!')
raise NotImplementedError('Method not implemented!')
def add_IamServiceServicer_to_server(servicer, server):
rpc_method_handlers = {
'Register': grpc.unary_unary_rpc_method_handler(
servicer.Register,
request_deserializer=iam__pb2.RegisterRequest.FromString,
response_serializer=iam__pb2.AuthResponse.SerializeToString,
),
'Login': grpc.unary_unary_rpc_method_handler(
servicer.Login,
request_deserializer=iam__pb2.LoginRequest.FromString,
response_serializer=iam__pb2.AuthResponse.SerializeToString,
),
'RefreshToken': grpc.unary_unary_rpc_method_handler(
servicer.RefreshToken,
request_deserializer=iam__pb2.RefreshTokenRequest.FromString,
response_serializer=iam__pb2.TokenPair.SerializeToString,
),
'Logout': grpc.unary_unary_rpc_method_handler(
servicer.Logout,
request_deserializer=iam__pb2.LogoutRequest.FromString,
response_serializer=iam__pb2.LogoutResponse.SerializeToString,
),
'GetUserInfo': grpc.unary_unary_rpc_method_handler(
servicer.GetUserInfo,
request_deserializer=iam__pb2.GetUserInfoRequest.FromString,
response_serializer=iam__pb2.UserInfo.SerializeToString,
),
'GetUserProfile': grpc.unary_unary_rpc_method_handler(
servicer.GetUserProfile,
request_deserializer=iam__pb2.GetUserProfileRequest.FromString,
response_serializer=iam__pb2.UserInfo.SerializeToString,
),
'UpdateProfile': grpc.unary_unary_rpc_method_handler(
servicer.UpdateProfile,
request_deserializer=iam__pb2.UpdateProfileRequest.FromString,
response_serializer=iam__pb2.UserInfo.SerializeToString,
),
'ChangePassword': grpc.unary_unary_rpc_method_handler(
servicer.ChangePassword,
request_deserializer=iam__pb2.ChangePasswordRequest.FromString,
response_serializer=iam__pb2.ChangePasswordResponse.SerializeToString,
),
'BatchGetUsers': grpc.unary_unary_rpc_method_handler(
servicer.BatchGetUsers,
request_deserializer=iam__pb2.BatchGetUsersRequest.FromString,
response_serializer=iam__pb2.BatchGetUsersResponse.SerializeToString,
),
'GetEffectivePermissions': grpc.unary_unary_rpc_method_handler(
servicer.GetEffectivePermissions,
request_deserializer=iam__pb2.GetEffectivePermissionsRequest.FromString,
response_serializer=iam__pb2.EffectivePermissionsResponse.SerializeToString,
),
'GetEffectiveAccess': grpc.unary_unary_rpc_method_handler(
servicer.GetEffectiveAccess,
request_deserializer=iam__pb2.GetEffectiveAccessRequest.FromString,
response_serializer=iam__pb2.EffectiveAccessResponse.SerializeToString,
),
'GetEffectiveDataScope': grpc.unary_unary_rpc_method_handler(
servicer.GetEffectiveDataScope,
request_deserializer=iam__pb2.GetEffectiveDataScopeRequest.FromString,
response_serializer=iam__pb2.EffectiveDataScope.SerializeToString,
),
'GetViewports': grpc.unary_unary_rpc_method_handler(
servicer.GetViewports,
request_deserializer=iam__pb2.GetViewportsRequest.FromString,
response_serializer=iam__pb2.ViewportsResponse.SerializeToString,
),
'GetPublicKey': grpc.unary_unary_rpc_method_handler(
servicer.GetPublicKey,
request_deserializer=iam__pb2.GetPublicKeyRequest.FromString,
response_serializer=iam__pb2.PublicKeyResponse.SerializeToString,
),
'GetChildrenByParent': grpc.unary_unary_rpc_method_handler(
servicer.GetChildrenByParent,
request_deserializer=iam__pb2.GetChildrenByParentRequest.FromString,
response_serializer=iam__pb2.ChildrenResponse.SerializeToString,
),
}
generic_handler = grpc.method_handlers_generic_handler(
'next_edu_cloud.iam.v1.IamService', rpc_method_handlers)
server.add_generic_rpc_handlers((generic_handler,))
server.add_registered_method_handlers('next_edu_cloud.iam.v1.IamService', rpc_method_handlers)
# This class is part of an EXPERIMENTAL API.
class IamService:
"""IamService 定义身份与访问管理契约
双入口策略president §2.16REST 供 gateway 透传 + admin-portal 直连,
gRPC 供 BFF 聚合调用。同一 Application Service 同时被两种 Controller 调用。
gRPC 端口 50052P2 即启用I1 裁决)。
"""
@staticmethod
def Register(request,
target,
options=(),
channel_credentials=None,
call_credentials=None,
insecure=False,
compression=None,
wait_for_ready=None,
timeout=None,
metadata=None):
return grpc.experimental.unary_unary(
request,
target,
'/next_edu_cloud.iam.v1.IamService/Register',
iam__pb2.RegisterRequest.SerializeToString,
iam__pb2.AuthResponse.FromString,
options,
channel_credentials,
insecure,
call_credentials,
compression,
wait_for_ready,
timeout,
metadata,
_registered_method=True)
@staticmethod
def Login(request,
target,
options=(),
channel_credentials=None,
call_credentials=None,
insecure=False,
compression=None,
wait_for_ready=None,
timeout=None,
metadata=None):
return grpc.experimental.unary_unary(
request,
target,
'/next_edu_cloud.iam.v1.IamService/Login',
iam__pb2.LoginRequest.SerializeToString,
iam__pb2.AuthResponse.FromString,
options,
channel_credentials,
insecure,
call_credentials,
compression,
wait_for_ready,
timeout,
metadata,
_registered_method=True)
@staticmethod
def RefreshToken(request,
target,
options=(),
channel_credentials=None,
call_credentials=None,
insecure=False,
compression=None,
wait_for_ready=None,
timeout=None,
metadata=None):
return grpc.experimental.unary_unary(
request,
target,
'/next_edu_cloud.iam.v1.IamService/RefreshToken',
iam__pb2.RefreshTokenRequest.SerializeToString,
iam__pb2.TokenPair.FromString,
options,
channel_credentials,
insecure,
call_credentials,
compression,
wait_for_ready,
timeout,
metadata,
_registered_method=True)
@staticmethod
def Logout(request,
target,
options=(),
channel_credentials=None,
call_credentials=None,
insecure=False,
compression=None,
wait_for_ready=None,
timeout=None,
metadata=None):
return grpc.experimental.unary_unary(
request,
target,
'/next_edu_cloud.iam.v1.IamService/Logout',
iam__pb2.LogoutRequest.SerializeToString,
iam__pb2.LogoutResponse.FromString,
options,
channel_credentials,
insecure,
call_credentials,
compression,
wait_for_ready,
timeout,
metadata,
_registered_method=True)
@staticmethod
def GetUserInfo(request,
target,
options=(),
channel_credentials=None,
call_credentials=None,
insecure=False,
compression=None,
wait_for_ready=None,
timeout=None,
metadata=None):
return grpc.experimental.unary_unary(
request,
target,
'/next_edu_cloud.iam.v1.IamService/GetUserInfo',
iam__pb2.GetUserInfoRequest.SerializeToString,
iam__pb2.UserInfo.FromString,
options,
channel_credentials,
insecure,
call_credentials,
compression,
wait_for_ready,
timeout,
metadata,
_registered_method=True)
@staticmethod
def GetUserProfile(request,
target,
options=(),
channel_credentials=None,
call_credentials=None,
insecure=False,
compression=None,
wait_for_ready=None,
timeout=None,
metadata=None):
return grpc.experimental.unary_unary(
request,
target,
'/next_edu_cloud.iam.v1.IamService/GetUserProfile',
iam__pb2.GetUserProfileRequest.SerializeToString,
iam__pb2.UserInfo.FromString,
options,
channel_credentials,
insecure,
call_credentials,
compression,
wait_for_ready,
timeout,
metadata,
_registered_method=True)
@staticmethod
def UpdateProfile(request,
target,
options=(),
channel_credentials=None,
call_credentials=None,
insecure=False,
compression=None,
wait_for_ready=None,
timeout=None,
metadata=None):
return grpc.experimental.unary_unary(
request,
target,
'/next_edu_cloud.iam.v1.IamService/UpdateProfile',
iam__pb2.UpdateProfileRequest.SerializeToString,
iam__pb2.UserInfo.FromString,
options,
channel_credentials,
insecure,
call_credentials,
compression,
wait_for_ready,
timeout,
metadata,
_registered_method=True)
@staticmethod
def ChangePassword(request,
target,
options=(),
channel_credentials=None,
call_credentials=None,
insecure=False,
compression=None,
wait_for_ready=None,
timeout=None,
metadata=None):
return grpc.experimental.unary_unary(
request,
target,
'/next_edu_cloud.iam.v1.IamService/ChangePassword',
iam__pb2.ChangePasswordRequest.SerializeToString,
iam__pb2.ChangePasswordResponse.FromString,
options,
channel_credentials,
insecure,
call_credentials,
compression,
wait_for_ready,
timeout,
metadata,
_registered_method=True)
@staticmethod
def BatchGetUsers(request,
target,
options=(),
channel_credentials=None,
call_credentials=None,
insecure=False,
compression=None,
wait_for_ready=None,
timeout=None,
metadata=None):
return grpc.experimental.unary_unary(
request,
target,
'/next_edu_cloud.iam.v1.IamService/BatchGetUsers',
iam__pb2.BatchGetUsersRequest.SerializeToString,
iam__pb2.BatchGetUsersResponse.FromString,
options,
channel_credentials,
insecure,
call_credentials,
compression,
wait_for_ready,
timeout,
metadata,
_registered_method=True)
@staticmethod
def GetEffectivePermissions(request,
target,
options=(),
channel_credentials=None,
call_credentials=None,
insecure=False,
compression=None,
wait_for_ready=None,
timeout=None,
metadata=None):
return grpc.experimental.unary_unary(
request,
target,
'/next_edu_cloud.iam.v1.IamService/GetEffectivePermissions',
iam__pb2.GetEffectivePermissionsRequest.SerializeToString,
iam__pb2.EffectivePermissionsResponse.FromString,
options,
channel_credentials,
insecure,
call_credentials,
compression,
wait_for_ready,
timeout,
metadata,
_registered_method=True)
@staticmethod
def GetEffectiveAccess(request,
target,
options=(),
channel_credentials=None,
call_credentials=None,
insecure=False,
compression=None,
wait_for_ready=None,
timeout=None,
metadata=None):
return grpc.experimental.unary_unary(
request,
target,
'/next_edu_cloud.iam.v1.IamService/GetEffectiveAccess',
iam__pb2.GetEffectiveAccessRequest.SerializeToString,
iam__pb2.EffectiveAccessResponse.FromString,
options,
channel_credentials,
insecure,
call_credentials,
compression,
wait_for_ready,
timeout,
metadata,
_registered_method=True)
@staticmethod
def GetEffectiveDataScope(request,
target,
options=(),
channel_credentials=None,
call_credentials=None,
insecure=False,
compression=None,
wait_for_ready=None,
timeout=None,
metadata=None):
return grpc.experimental.unary_unary(
request,
target,
'/next_edu_cloud.iam.v1.IamService/GetEffectiveDataScope',
iam__pb2.GetEffectiveDataScopeRequest.SerializeToString,
iam__pb2.EffectiveDataScope.FromString,
options,
channel_credentials,
insecure,
call_credentials,
compression,
wait_for_ready,
timeout,
metadata,
_registered_method=True)
@staticmethod
def GetViewports(request,
target,
options=(),
channel_credentials=None,
call_credentials=None,
insecure=False,
compression=None,
wait_for_ready=None,
timeout=None,
metadata=None):
return grpc.experimental.unary_unary(
request,
target,
'/next_edu_cloud.iam.v1.IamService/GetViewports',
iam__pb2.GetViewportsRequest.SerializeToString,
iam__pb2.ViewportsResponse.FromString,
options,
channel_credentials,
insecure,
call_credentials,
compression,
wait_for_ready,
timeout,
metadata,
_registered_method=True)
@staticmethod
def GetPublicKey(request,
target,
options=(),
channel_credentials=None,
call_credentials=None,
insecure=False,
compression=None,
wait_for_ready=None,
timeout=None,
metadata=None):
return grpc.experimental.unary_unary(
request,
target,
'/next_edu_cloud.iam.v1.IamService/GetPublicKey',
iam__pb2.GetPublicKeyRequest.SerializeToString,
iam__pb2.PublicKeyResponse.FromString,
options,
channel_credentials,
insecure,
call_credentials,
compression,
wait_for_ready,
timeout,
metadata,
_registered_method=True)
@staticmethod
def GetChildrenByParent(request,
target,
options=(),
channel_credentials=None,
call_credentials=None,
insecure=False,
compression=None,
wait_for_ready=None,
timeout=None,
metadata=None):
return grpc.experimental.unary_unary(
request,
target,
'/next_edu_cloud.iam.v1.IamService/GetChildrenByParent',
iam__pb2.GetChildrenByParentRequest.SerializeToString,
iam__pb2.ChildrenResponse.FromString,
options,
channel_credentials,
insecure,
call_credentials,
compression,
wait_for_ready,
timeout,
metadata,
_registered_method=True)